From b25fa862d5f7de6c20fa4b95ce5c85962669fd3b Mon Sep 17 00:00:00 2001 From: LinPr <314573849@qq.com> Date: Thu, 31 Jul 2025 22:46:40 +0800 Subject: [PATCH 001/571] fix: assign KUBECONFIG environment variable value to env.Kubeconfig Signed-off-by: LinPr <314573849@qq.com> --- pkg/cli/environment.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 3f2dc00b2..85623dfdd 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -95,6 +95,7 @@ func New() *EnvSettings { env := &EnvSettings{ namespace: os.Getenv("HELM_NAMESPACE"), MaxHistory: envIntOr("HELM_MAX_HISTORY", defaultMaxHistory), + KubeConfig: os.Getenv("KUBECONFIG"), KubeContext: os.Getenv("HELM_KUBECONTEXT"), KubeToken: os.Getenv("HELM_KUBETOKEN"), KubeAsUser: os.Getenv("HELM_KUBEASUSER"), From 1836c598f06377fd1571702fb2e0642f004cedef Mon Sep 17 00:00:00 2001 From: Andreas Sommer Date: Wed, 3 Jul 2024 12:45:08 +0200 Subject: [PATCH 002/571] Make error message instructional for the case of lock file being out of date Signed-off-by: Andreas Sommer --- pkg/cmd/install.go | 2 +- pkg/cmd/testdata/output/upgrade-with-missing-dependencies.txt | 2 +- pkg/cmd/upgrade.go | 2 +- pkg/downloader/manager.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index 361d91e5f..8a27627bd 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -304,7 +304,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options return nil, fmt.Errorf("failed reloading chart after repo update: %w", err) } } else { - return nil, fmt.Errorf("an error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: %w", err) + return nil, fmt.Errorf("an error occurred while checking for chart dependencies. You may need to run 'helm dependency build' to fetch missing dependencies: %w", err) } } } diff --git a/pkg/cmd/testdata/output/upgrade-with-missing-dependencies.txt b/pkg/cmd/testdata/output/upgrade-with-missing-dependencies.txt index b2c154a80..cb0a3a167 100644 --- a/pkg/cmd/testdata/output/upgrade-with-missing-dependencies.txt +++ b/pkg/cmd/testdata/output/upgrade-with-missing-dependencies.txt @@ -1 +1 @@ -Error: an error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: found in Chart.yaml, but missing in charts/ directory: reqsubchart2 +Error: an error occurred while checking for chart dependencies. You may need to run 'helm dependency build' to fetch missing dependencies: found in Chart.yaml, but missing in charts/ directory: reqsubchart2 diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index 74061caf7..2e709a12d 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -200,7 +200,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } if req := ch.Metadata.Dependencies; req != nil { if err := action.CheckDependencies(ch, req); err != nil { - err = fmt.Errorf("an error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: %w", err) + err = fmt.Errorf("an error occurred while checking for chart dependencies. You may need to run 'helm dependency build' to fetch missing dependencies: %w", err) if client.DependencyUpdate { man := &downloader.Manager{ Out: out, diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 8b77a77c0..32d454e81 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -127,7 +127,7 @@ func (m *Manager) Build() error { return errors.New("the lock file (requirements.lock) is out of sync with the dependencies file (requirements.yaml). Please update the dependencies") } } else { - return errors.New("the lock file (Chart.lock) is out of sync with the dependencies file (Chart.yaml). Please update the dependencies") + return errors.New("the lock file (Chart.lock) is out of sync with the dependencies file (Chart.yaml). Please update the dependencies with 'helm dependency update'") } } From 8068578266e87b1b587b4c05cc82eb98e797d992 Mon Sep 17 00:00:00 2001 From: zyfy29 Date: Thu, 28 Aug 2025 09:25:15 +0900 Subject: [PATCH 003/571] chore: delete unused var in installer.go Signed-off-by: zyfy29 --- internal/plugin/installer/installer.go | 3 --- pkg/cmd/plugin_install.go | 2 -- pkg/cmd/plugin_update.go | 1 - 3 files changed, 6 deletions(-) diff --git a/internal/plugin/installer/installer.go b/internal/plugin/installer/installer.go index 7900f6745..9d88aeca1 100644 --- a/internal/plugin/installer/installer.go +++ b/internal/plugin/installer/installer.go @@ -28,9 +28,6 @@ import ( // ErrMissingMetadata indicates that plugin.yaml is missing. var ErrMissingMetadata = errors.New("plugin metadata (plugin.yaml) missing") -// Debug enables verbose output. -var Debug bool - // Installer provides an interface for installing helm client plugins. type Installer interface { // Install adds a plugin. diff --git a/pkg/cmd/plugin_install.go b/pkg/cmd/plugin_install.go index 960404a76..a1001fdd2 100644 --- a/pkg/cmd/plugin_install.go +++ b/pkg/cmd/plugin_install.go @@ -107,8 +107,6 @@ func (o *pluginInstallOptions) newInstallerForSource() (installer.Installer, err } func (o *pluginInstallOptions) run(out io.Writer) error { - installer.Debug = settings.Debug - i, err := o.newInstallerForSource() if err != nil { return err diff --git a/pkg/cmd/plugin_update.go b/pkg/cmd/plugin_update.go index c6d4b8530..6cc2729fc 100644 --- a/pkg/cmd/plugin_update.go +++ b/pkg/cmd/plugin_update.go @@ -61,7 +61,6 @@ func (o *pluginUpdateOptions) complete(args []string) error { } func (o *pluginUpdateOptions) run(out io.Writer) error { - installer.Debug = settings.Debug slog.Debug("loading installed plugins", "path", settings.PluginsDirectory) plugins, err := plugin.LoadAll(settings.PluginsDirectory) if err != nil { From 6181e0a873688e42e935147e0e3cb3769a067120 Mon Sep 17 00:00:00 2001 From: Tom Wieczorek Date: Mon, 22 May 2023 11:05:32 +0200 Subject: [PATCH 004/571] Make test scripts run without /bin/bash The test scripts hardcoded #!/bin/bash while they are not really requiring bash. Use the more portable #!/usr/bin/env sh instead, so that they use the default shell. Signed-off-by: Tom Wieczorek --- internal/plugin/installer/http_installer_test.go | 4 ++-- internal/plugin/installer/local_installer_test.go | 2 +- internal/plugin/testdata/plugdir/good/hello-legacy/hello.sh | 4 ++-- internal/plugin/testdata/plugdir/good/hello-v1/hello.sh | 4 ++-- pkg/cmd/testdata/helmhome/helm/plugins/args/args.sh | 4 ++-- pkg/cmd/testdata/helmhome/helm/plugins/exitwith/exitwith.sh | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/internal/plugin/installer/http_installer_test.go b/internal/plugin/installer/http_installer_test.go index be40b1b90..7f7e6cef6 100644 --- a/internal/plugin/installer/http_installer_test.go +++ b/internal/plugin/installer/http_installer_test.go @@ -368,7 +368,7 @@ func TestExtractWithNestedDirectories(t *testing.T) { }{ {"plugin.yaml", "plugin metadata", 0600, tar.TypeReg}, {"bin/", "", 0755, tar.TypeDir}, - {"bin/plugin", "#!/bin/bash\necho plugin", 0755, tar.TypeReg}, + {"bin/plugin", "#!/usr/bin/env sh\necho plugin", 0755, tar.TypeReg}, {"docs/", "", 0755, tar.TypeDir}, {"docs/README.md", "readme content", 0644, tar.TypeReg}, {"docs/examples/", "", 0755, tar.TypeDir}, @@ -531,7 +531,7 @@ func TestExtractPluginInSubdirectory(t *testing.T) { {"my-plugin/", "", 0755, tar.TypeDir}, {"my-plugin/plugin.yaml", "name: my-plugin\nversion: 1.0.0\nusage: test\ndescription: test plugin\ncommand: $HELM_PLUGIN_DIR/bin/my-plugin", 0644, tar.TypeReg}, {"my-plugin/bin/", "", 0755, tar.TypeDir}, - {"my-plugin/bin/my-plugin", "#!/bin/bash\necho test", 0755, tar.TypeReg}, + {"my-plugin/bin/my-plugin", "#!/usr/bin/env sh\necho test", 0755, tar.TypeReg}, } for _, file := range files { diff --git a/internal/plugin/installer/local_installer_test.go b/internal/plugin/installer/local_installer_test.go index 189108fdb..608ee8ff8 100644 --- a/internal/plugin/installer/local_installer_test.go +++ b/internal/plugin/installer/local_installer_test.go @@ -87,7 +87,7 @@ func TestLocalInstallerTarball(t *testing.T) { Mode int64 }{ {"test-plugin/plugin.yaml", "name: test-plugin\napiVersion: v1\ntype: cli/v1\nruntime: subprocess\nversion: 1.0.0\nconfig:\n shortHelp: test\n longHelp: test\nruntimeConfig:\n platformCommand:\n - command: echo", 0644}, - {"test-plugin/bin/test-plugin", "#!/bin/bash\necho test", 0755}, + {"test-plugin/bin/test-plugin", "#!/usr/bin/env sh\necho test", 0755}, } for _, file := range files { diff --git a/internal/plugin/testdata/plugdir/good/hello-legacy/hello.sh b/internal/plugin/testdata/plugdir/good/hello-legacy/hello.sh index dcfd58876..4f20796ef 100755 --- a/internal/plugin/testdata/plugdir/good/hello-legacy/hello.sh +++ b/internal/plugin/testdata/plugdir/good/hello-legacy/hello.sh @@ -1,9 +1,9 @@ -#!/bin/bash +#!/usr/bin/env sh echo "Hello from a Helm plugin" echo "PARAMS" -echo $* +echo "$@" $HELM_BIN ls --all diff --git a/internal/plugin/testdata/plugdir/good/hello-v1/hello.sh b/internal/plugin/testdata/plugdir/good/hello-v1/hello.sh index dcfd58876..4f20796ef 100755 --- a/internal/plugin/testdata/plugdir/good/hello-v1/hello.sh +++ b/internal/plugin/testdata/plugdir/good/hello-v1/hello.sh @@ -1,9 +1,9 @@ -#!/bin/bash +#!/usr/bin/env sh echo "Hello from a Helm plugin" echo "PARAMS" -echo $* +echo "$@" $HELM_BIN ls --all diff --git a/pkg/cmd/testdata/helmhome/helm/plugins/args/args.sh b/pkg/cmd/testdata/helmhome/helm/plugins/args/args.sh index 678b4eff5..6c62be8b9 100755 --- a/pkg/cmd/testdata/helmhome/helm/plugins/args/args.sh +++ b/pkg/cmd/testdata/helmhome/helm/plugins/args/args.sh @@ -1,2 +1,2 @@ -#!/bin/bash -echo $* +#!/usr/bin/env sh +echo "$@" diff --git a/pkg/cmd/testdata/helmhome/helm/plugins/exitwith/exitwith.sh b/pkg/cmd/testdata/helmhome/helm/plugins/exitwith/exitwith.sh index ec8469657..9cf68da68 100755 --- a/pkg/cmd/testdata/helmhome/helm/plugins/exitwith/exitwith.sh +++ b/pkg/cmd/testdata/helmhome/helm/plugins/exitwith/exitwith.sh @@ -1,2 +1,2 @@ -#!/bin/bash -exit $* +#!/usr/bin/env sh +exit "$1" From 6ef79bb8d54bd4881e60d3e78d633b021f1079ec Mon Sep 17 00:00:00 2001 From: Paul Van Laer Date: Thu, 30 Oct 2025 18:00:08 +0100 Subject: [PATCH 005/571] feat(repo): add --no-headers option to 'helm repo list' This adds a --no-headers flag to the 'helm repo list' command, allowing users to suppress table headers in the output. Useful for scripting and automation. Signed-off-by: Paul Van Laer --- pkg/cmd/repo_list.go | 17 +++++++++++++---- pkg/cmd/repo_list_test.go | 6 ++++++ .../testdata/output/repo-list-no-headers.txt | 3 +++ 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 pkg/cmd/testdata/output/repo-list-no-headers.txt diff --git a/pkg/cmd/repo_list.go b/pkg/cmd/repo_list.go index 10b4442a0..60f4cb6c6 100644 --- a/pkg/cmd/repo_list.go +++ b/pkg/cmd/repo_list.go @@ -30,6 +30,7 @@ import ( func newRepoListCmd(out io.Writer) *cobra.Command { var outfmt output.Format + var noHeaders bool cmd := &cobra.Command{ Use: "list", Aliases: []string{"ls"}, @@ -46,12 +47,17 @@ func newRepoListCmd(out io.Writer) *cobra.Command { return nil } - return outfmt.Write(out, &repoListWriter{f.Repositories}) + w := &repoListWriter{ + repos: f.Repositories, + noHeaders: noHeaders, + } + + return outfmt.Write(out, w) }, } + cmd.Flags().BoolVar(&noHeaders, "no-headers", false, "suppress headers in the output") bindOutputFlag(cmd, &outfmt) - return cmd } @@ -61,12 +67,15 @@ type repositoryElement struct { } type repoListWriter struct { - repos []*repo.Entry + repos []*repo.Entry + noHeaders bool } func (r *repoListWriter) WriteTable(out io.Writer) error { table := uitable.New() - table.AddRow("NAME", "URL") + if !r.noHeaders { + table.AddRow("NAME", "URL") + } for _, re := range r.repos { table.AddRow(re.Name, re.URL) } diff --git a/pkg/cmd/repo_list_test.go b/pkg/cmd/repo_list_test.go index 2f6a9e4ad..94cdf3969 100644 --- a/pkg/cmd/repo_list_test.go +++ b/pkg/cmd/repo_list_test.go @@ -48,6 +48,12 @@ func TestRepoList(t *testing.T) { golden: "output/repo-list.txt", wantError: false, }, + { + name: "list without headers", + cmd: fmt.Sprintf("repo list --repository-config %s --repository-cache %s --no-headers", repoFile2, rootDir), + golden: "output/repo-list-no-headers.txt", + wantError: false, + }, } runTestCmd(t, tests) diff --git a/pkg/cmd/testdata/output/repo-list-no-headers.txt b/pkg/cmd/testdata/output/repo-list-no-headers.txt new file mode 100644 index 000000000..13491aeb2 --- /dev/null +++ b/pkg/cmd/testdata/output/repo-list-no-headers.txt @@ -0,0 +1,3 @@ +charts https://charts.helm.sh/stable +firstexample http://firstexample.com +secondexample http://secondexample.com From 558cea7458a80d0b6b142625a3bc67ddc8fd3c64 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Thu, 30 Oct 2025 21:08:08 +0000 Subject: [PATCH 006/571] chore: increase logging package test coverage Signed-off-by: Evans Mungai --- internal/logging/logging.go | 3 + internal/logging/logging_test.go | 258 +++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+) diff --git a/internal/logging/logging.go b/internal/logging/logging.go index b8faf859e..674e2db34 100644 --- a/internal/logging/logging.go +++ b/internal/logging/logging.go @@ -36,6 +36,9 @@ type DebugCheckHandler struct { // Enabled implements slog.Handler.Enabled func (h *DebugCheckHandler) Enabled(_ context.Context, level slog.Level) bool { if level == slog.LevelDebug { + if h.debugEnabled == nil { + return false + } return h.debugEnabled() } return true // Always log other levels diff --git a/internal/logging/logging_test.go b/internal/logging/logging_test.go index 75e6c4025..d22a47a31 100644 --- a/internal/logging/logging_test.go +++ b/internal/logging/logging_test.go @@ -18,8 +18,10 @@ package logging import ( "bytes" + "context" "log/slog" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -113,3 +115,259 @@ func TestLogHolder_InterfaceCompliance(t *testing.T) { assert.Equal(t, handler, logger.Handler()) }) } + +func TestDebugCheckHandler_Enabled(t *testing.T) { + t.Run("returns debugEnabled function result for debug level", func(t *testing.T) { + // Test with debug enabled + debugEnabled := func() bool { return true } + buf := &bytes.Buffer{} + baseHandler := slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}) + handler := &DebugCheckHandler{ + handler: baseHandler, + debugEnabled: debugEnabled, + } + + assert.True(t, handler.Enabled(t.Context(), slog.LevelDebug)) + }) + + t.Run("returns false for debug level when debug disabled", func(t *testing.T) { + // Test with debug disabled + debugEnabled := func() bool { return false } + buf := &bytes.Buffer{} + baseHandler := slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}) + handler := &DebugCheckHandler{ + handler: baseHandler, + debugEnabled: debugEnabled, + } + + assert.False(t, handler.Enabled(t.Context(), slog.LevelDebug)) + }) + + t.Run("always returns true for non-debug levels", func(t *testing.T) { + debugEnabled := func() bool { return false } // Debug disabled + buf := &bytes.Buffer{} + baseHandler := slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}) + handler := &DebugCheckHandler{ + handler: baseHandler, + debugEnabled: debugEnabled, + } + + // Even with debug disabled, other levels should always be enabled + assert.True(t, handler.Enabled(t.Context(), slog.LevelInfo)) + assert.True(t, handler.Enabled(t.Context(), slog.LevelWarn)) + assert.True(t, handler.Enabled(t.Context(), slog.LevelError)) + }) + + t.Run("calls debugEnabled function dynamically", func(t *testing.T) { + callCount := 0 + debugEnabled := func() bool { + callCount++ + return callCount%2 == 1 // Alternates between true and false + } + + buf := &bytes.Buffer{} + baseHandler := slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}) + handler := &DebugCheckHandler{ + handler: baseHandler, + debugEnabled: debugEnabled, + } + + // First call should return true + assert.True(t, handler.Enabled(t.Context(), slog.LevelDebug)) + assert.Equal(t, 1, callCount) + + // Second call should return false + assert.False(t, handler.Enabled(t.Context(), slog.LevelDebug)) + assert.Equal(t, 2, callCount) + + // Third call should return true again + assert.True(t, handler.Enabled(t.Context(), slog.LevelDebug)) + assert.Equal(t, 3, callCount) + }) +} + +func TestDebugCheckHandler_Handle(t *testing.T) { + t.Run("delegates to underlying handler", func(t *testing.T) { + buf := &bytes.Buffer{} + baseHandler := slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}) + handler := &DebugCheckHandler{ + handler: baseHandler, + debugEnabled: func() bool { return true }, + } + + record := slog.NewRecord(time.Now(), slog.LevelInfo, "test message", 0) + err := handler.Handle(t.Context(), record) + + assert.NoError(t, err) + assert.Contains(t, buf.String(), "test message") + }) + + t.Run("handles context correctly", func(t *testing.T) { + buf := &bytes.Buffer{} + baseHandler := slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}) + handler := &DebugCheckHandler{ + handler: baseHandler, + debugEnabled: func() bool { return true }, + } + + type testKey string + ctx := context.WithValue(t.Context(), testKey("test"), "value") + record := slog.NewRecord(time.Now(), slog.LevelInfo, "context test", 0) + err := handler.Handle(ctx, record) + + assert.NoError(t, err) + assert.Contains(t, buf.String(), "context test") + }) +} + +func TestDebugCheckHandler_WithAttrs(t *testing.T) { + t.Run("returns new DebugCheckHandler with attributes", func(t *testing.T) { + logger := NewLogger(func() bool { return true }) + handler := logger.Handler() + newHandler := handler.WithAttrs([]slog.Attr{ + slog.String("key1", "value1"), + slog.Int("key2", 42), + }) + + // Should return a DebugCheckHandler + debugHandler, ok := newHandler.(*DebugCheckHandler) + assert.True(t, ok) + assert.NotNil(t, debugHandler) + + // Should preserve the debugEnabled function + assert.True(t, debugHandler.Enabled(t.Context(), slog.LevelDebug)) + + // Should have the attributes applied to the underlying handler + assert.NotEqual(t, handler, debugHandler.handler) + }) + + t.Run("preserves debugEnabled function", func(t *testing.T) { + callCount := 0 + debugEnabled := func() bool { + callCount++ + return callCount%2 == 1 + } + + buf := &bytes.Buffer{} + baseHandler := slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}) + handler := &DebugCheckHandler{ + handler: baseHandler, + debugEnabled: debugEnabled, + } + + attrs := []slog.Attr{slog.String("test", "value")} + newHandler := handler.WithAttrs(attrs) + + // The new handler should use the same debugEnabled function + assert.True(t, newHandler.Enabled(t.Context(), slog.LevelDebug)) + assert.Equal(t, 1, callCount) + + assert.False(t, newHandler.Enabled(t.Context(), slog.LevelDebug)) + assert.Equal(t, 2, callCount) + }) +} + +func TestDebugCheckHandler_WithGroup(t *testing.T) { + t.Run("returns new DebugCheckHandler with group", func(t *testing.T) { + buf := &bytes.Buffer{} + baseHandler := slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}) + handler := &DebugCheckHandler{ + handler: baseHandler, + debugEnabled: func() bool { return true }, + } + + newHandler := handler.WithGroup("testgroup") + + // Should return a DebugCheckHandler + debugHandler, ok := newHandler.(*DebugCheckHandler) + assert.True(t, ok) + assert.NotNil(t, debugHandler) + + // Should preserve the debugEnabled function + assert.True(t, debugHandler.Enabled(t.Context(), slog.LevelDebug)) + + // Should have the group applied to the underlying handler + assert.NotEqual(t, handler.handler, debugHandler.handler) + }) + + t.Run("preserves debugEnabled function", func(t *testing.T) { + callCount := 0 + debugEnabled := func() bool { + callCount++ + return callCount%2 == 1 + } + + buf := &bytes.Buffer{} + baseHandler := slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}) + handler := &DebugCheckHandler{ + handler: baseHandler, + debugEnabled: debugEnabled, + } + + newHandler := handler.WithGroup("testgroup") + + // The new handler should use the same debugEnabled function + assert.True(t, newHandler.Enabled(t.Context(), slog.LevelDebug)) + assert.Equal(t, 1, callCount) + + assert.False(t, newHandler.Enabled(t.Context(), slog.LevelDebug)) + assert.Equal(t, 2, callCount) + }) +} + +func TestDebugCheckHandler_Integration(t *testing.T) { + t.Run("works with NewLogger function", func(t *testing.T) { + debugEnabled := func() bool { return true } + logger := NewLogger(debugEnabled) + + assert.NotNil(t, logger) + + // The logger should have a DebugCheckHandler + handler := logger.Handler() + debugHandler, ok := handler.(*DebugCheckHandler) + assert.True(t, ok) + + // Should enable debug when debugEnabled returns true + assert.True(t, debugHandler.Enabled(t.Context(), slog.LevelDebug)) + + // Should enable other levels regardless + assert.True(t, debugHandler.Enabled(t.Context(), slog.LevelInfo)) + }) + + t.Run("dynamic debug checking works in practice", func(t *testing.T) { + debugState := false + debugEnabled := func() bool { return debugState } + + logger := NewLogger(debugEnabled) + + // Initially debug should be disabled + assert.False(t, logger.Handler().(*DebugCheckHandler).Enabled(t.Context(), slog.LevelDebug)) + + // Enable debug + debugState = true + assert.True(t, logger.Handler().(*DebugCheckHandler).Enabled(t.Context(), slog.LevelDebug)) + + // Disable debug again + debugState = false + assert.False(t, logger.Handler().(*DebugCheckHandler).Enabled(t.Context(), slog.LevelDebug)) + }) + + t.Run("handles nil debugEnabled function", func(t *testing.T) { + logger := NewLogger(nil) + + assert.NotNil(t, logger) + + // The logger should have a DebugCheckHandler + handler := logger.Handler() + debugHandler, ok := handler.(*DebugCheckHandler) + assert.True(t, ok) + + // When debugEnabled is nil, debug level should be disabled (default behavior) + assert.False(t, debugHandler.Enabled(t.Context(), slog.LevelDebug)) + + // Other levels should always be enabled + assert.True(t, debugHandler.Enabled(t.Context(), slog.LevelInfo)) + assert.True(t, debugHandler.Enabled(t.Context(), slog.LevelWarn)) + assert.True(t, debugHandler.Enabled(t.Context(), slog.LevelError)) + }) +} From 0f5eda74c1a0cf49d3a6e52ad3959ad1cfb2af55 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Thu, 6 Nov 2025 09:06:23 -0500 Subject: [PATCH 007/571] Adding a LoadArchive to common loader LoadArchive is in the individual loaders for the chart versions. It is used by SDK users to load a stream rather than a file on the filesystem. Adding to support SDK users like Flux. Signed-off-by: Matt Farina --- pkg/chart/loader/load.go | 33 ++++++ pkg/chart/loader/load_test.go | 187 ++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 pkg/chart/loader/load_test.go diff --git a/pkg/chart/loader/load.go b/pkg/chart/loader/load.go index 7a5ddbca9..dbc5d3004 100644 --- a/pkg/chart/loader/load.go +++ b/pkg/chart/loader/load.go @@ -20,6 +20,7 @@ import ( "compress/gzip" "errors" "fmt" + "io" "os" "path/filepath" @@ -156,6 +157,38 @@ func LoadFile(name string) (chart.Charter, error) { return nil, errors.New("unable to detect chart version, no Chart.yaml found") } +// LoadArchive loads from a reader containing a compressed tar archive. +func LoadArchive(in io.Reader) (chart.Charter, error) { + // Note: This function is for use by SDK users such as Flux. + + files, err := archive.LoadArchiveFiles(in) + if err != nil { + if err == gzip.ErrHeader { + return nil, fmt.Errorf("stream does not appear to be a valid chart file (details: %w)", err) + } + return nil, fmt.Errorf("unable to load chart archive: %w", err) + } + + for _, f := range files { + if f.Name == "Chart.yaml" { + c := new(chartBase) + if err := yaml.Unmarshal(f.Data, c); err != nil { + return c, fmt.Errorf("cannot load Chart.yaml: %w", err) + } + switch c.APIVersion { + case c2.APIVersionV1, c2.APIVersionV2, "": + return c2load.LoadFiles(files) + case c3.APIVersionV3: + return c3load.LoadFiles(files) + default: + return nil, errors.New("unsupported chart version") + } + } + } + + return nil, errors.New("unable to detect chart version, no Chart.yaml found") +} + // chartBase is used to detect the API Version for the chart to run it through the // loader for that type. type chartBase struct { diff --git a/pkg/chart/loader/load_test.go b/pkg/chart/loader/load_test.go new file mode 100644 index 000000000..401772557 --- /dev/null +++ b/pkg/chart/loader/load_test.go @@ -0,0 +1,187 @@ +/* +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 loader + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "fmt" + "io" + "path/filepath" + "strings" + "testing" + "time" + + c3 "helm.sh/helm/v4/internal/chart/v3" + "helm.sh/helm/v4/pkg/chart" + c2 "helm.sh/helm/v4/pkg/chart/v2" +) + +// createChartArchive is a helper function to create a gzipped tar archive in memory +func createChartArchive(t *testing.T, chartName, apiVersion string, extraFiles map[string][]byte, createChartYaml bool) io.Reader { + t.Helper() + var buf bytes.Buffer + gw := gzip.NewWriter(&buf) + tw := tar.NewWriter(gw) + + files := make(map[string][]byte) + for k, v := range extraFiles { + files[k] = v + } + + if createChartYaml { + chartYAMLContent := fmt.Sprintf(`apiVersion: %s +name: %s +version: 0.1.0 +description: A test chart +`, apiVersion, chartName) + files["Chart.yaml"] = []byte(chartYAMLContent) + } + + for name, data := range files { + header := &tar.Header{ + Name: filepath.Join(chartName, name), + Mode: 0644, + Size: int64(len(data)), + ModTime: time.Now(), + } + if err := tw.WriteHeader(header); err != nil { + t.Fatalf("Failed to write tar header for %s: %v", name, err) + } + if _, err := tw.Write(data); err != nil { + t.Fatalf("Failed to write tar data for %s: %v", name, err) + } + } + + if err := tw.Close(); err != nil { + t.Fatalf("Failed to close tar writer: %v", err) + } + if err := gw.Close(); err != nil { + t.Fatalf("Failed to close gzip writer: %v", err) + } + return &buf +} + +func TestLoadArchive(t *testing.T) { + testCases := []struct { + name string + chartName string + apiVersion string + extraFiles map[string][]byte + inputReader io.Reader + expectedChart chart.Charter + expectedError string + createChartYaml bool + }{ + { + name: "valid v2 chart archive", + chartName: "mychart-v2", + apiVersion: c2.APIVersionV2, + extraFiles: map[string][]byte{"templates/config.yaml": []byte("key: value")}, + expectedChart: &c2.Chart{ + Metadata: &c2.Metadata{APIVersion: c2.APIVersionV2, Name: "mychart-v2", Version: "0.1.0", Description: "A test chart"}, + }, + createChartYaml: true, + }, + { + name: "valid v3 chart archive", + chartName: "mychart-v3", + apiVersion: c3.APIVersionV3, + extraFiles: map[string][]byte{"templates/config.yaml": []byte("key: value")}, + expectedChart: &c3.Chart{ + Metadata: &c3.Metadata{APIVersion: c3.APIVersionV3, Name: "mychart-v3", Version: "0.1.0", Description: "A test chart"}, + }, + createChartYaml: true, + }, + { + name: "invalid gzip header", + inputReader: bytes.NewBufferString("not a gzip file"), + expectedError: "stream does not appear to be a valid chart file (details: gzip: invalid header)", + }, + { + name: "archive without Chart.yaml", + chartName: "no-chart-yaml", + apiVersion: c2.APIVersionV2, // This will be ignored as Chart.yaml is missing + extraFiles: map[string][]byte{"values.yaml": []byte("foo: bar")}, + expectedError: "unable to detect chart version, no Chart.yaml found", + createChartYaml: false, + }, + { + name: "archive with malformed Chart.yaml", + chartName: "malformed-chart-yaml", + apiVersion: c2.APIVersionV2, + extraFiles: map[string][]byte{"Chart.yaml": []byte("apiVersion: v2\nname: mychart\nversion: 0.1.0\ndescription: A test chart\ninvalid: :")}, + expectedError: "cannot load Chart.yaml: error converting YAML to JSON: yaml: line 5: mapping values are not allowed in this context", + createChartYaml: false, + }, + { + name: "unsupported API version", + chartName: "unsupported-api", + apiVersion: "v99", + expectedError: "unsupported chart version", + createChartYaml: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var reader io.Reader + if tc.inputReader != nil { + reader = tc.inputReader + } else { + reader = createChartArchive(t, tc.chartName, tc.apiVersion, tc.extraFiles, tc.createChartYaml) + } + + loadedChart, err := LoadArchive(reader) + + if tc.expectedError != "" { + if err == nil || !strings.Contains(err.Error(), tc.expectedError) { + t.Errorf("Expected error containing %q, but got %v", tc.expectedError, err) + } + return + } + + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + lac, err := chart.NewAccessor(loadedChart) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + eac, err := chart.NewAccessor(tc.expectedChart) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if lac.Name() != eac.Name() { + t.Errorf("Expected chart name %q, got %q", eac.Name(), lac.Name()) + } + + var loadedAPIVersion string + switch lc := loadedChart.(type) { + case *c2.Chart: + loadedAPIVersion = lc.Metadata.APIVersion + case *c3.Chart: + loadedAPIVersion = lc.Metadata.APIVersion + } + if loadedAPIVersion != tc.apiVersion { + t.Errorf("Expected API version %q, got %q", tc.apiVersion, loadedAPIVersion) + } + }) + } +} From ed6cf0e8a168517715262cc05409676858f0e26b Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Thu, 6 Nov 2025 15:42:26 +0100 Subject: [PATCH 008/571] Properly test error messages on pull command's test Signed-off-by: Benoit Tigeot --- pkg/cmd/pull_test.go | 49 ++++++++++-------- pkg/cmd/testdata/testcharts/test-0.1.0.tgz | Bin 0 -> 319 bytes pkg/cmd/testdata/testcharts/test/Chart.yaml | 4 ++ pkg/cmd/testdata/testcharts/test/values.yaml | 1 + pkg/cmd/testdata/testcharts/test1-0.1.0.tgz | Bin 0 -> 327 bytes pkg/cmd/testdata/testcharts/test1/Chart.yaml | 4 ++ pkg/cmd/testdata/testcharts/test1/values.yaml | 2 + 7 files changed, 38 insertions(+), 22 deletions(-) create mode 100644 pkg/cmd/testdata/testcharts/test-0.1.0.tgz create mode 100644 pkg/cmd/testdata/testcharts/test/Chart.yaml create mode 100644 pkg/cmd/testdata/testcharts/test/values.yaml create mode 100644 pkg/cmd/testdata/testcharts/test1-0.1.0.tgz create mode 100644 pkg/cmd/testdata/testcharts/test1/Chart.yaml create mode 100644 pkg/cmd/testdata/testcharts/test1/values.yaml diff --git a/pkg/cmd/pull_test.go b/pkg/cmd/pull_test.go index c24bf33b7..96631fe05 100644 --- a/pkg/cmd/pull_test.go +++ b/pkg/cmd/pull_test.go @@ -106,16 +106,16 @@ func TestPullCmd(t *testing.T) { { name: "Fetch untar when file with same name existed", args: "test/test1 --untar --untardir test1", - existFile: "test1", + existFile: "test1/test1", wantError: true, - wantErrorMsg: fmt.Sprintf("failed to untar: a file or directory with the name %s already exists", filepath.Join(srv.Root(), "test1")), + wantErrorMsg: fmt.Sprintf("failed to untar: a file or directory with the name %s already exists", filepath.Join(srv.Root(), "test1", "test1")), }, { name: "Fetch untar when dir with same name existed", - args: "test/test2 --untar --untardir test2", - existDir: "test2", + args: "test/test --untar --untardir test2", + existDir: "test2/test", wantError: true, - wantErrorMsg: fmt.Sprintf("failed to untar: a file or directory with the name %s already exists", filepath.Join(srv.Root(), "test2")), + wantErrorMsg: fmt.Sprintf("failed to untar: a file or directory with the name %s already exists", filepath.Join(srv.Root(), "test2", "test")), }, { name: "Fetch, verify, untar", @@ -178,9 +178,10 @@ func TestPullCmd(t *testing.T) { }, { name: "OCI Fetch untar when dir with same name existed", - args: fmt.Sprintf("oci-test-chart oci://%s/u/ocitestuser/oci-dependent-chart --version 0.1.0 --untar --untardir ocitest2 --untar --untardir ocitest2", ociSrv.RegistryURL), + args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart --version 0.1.0 --untar --untardir ocitest2", ociSrv.RegistryURL), + existDir: "ocitest2/oci-dependent-chart", wantError: true, - wantErrorMsg: fmt.Sprintf("failed to untar: a file or directory with the name %s already exists", filepath.Join(srv.Root(), "ocitest2")), + wantErrorMsg: fmt.Sprintf("failed to untar: a file or directory with the name %s already exists", filepath.Join(srv.Root(), "ocitest2", "oci-dependent-chart")), }, { name: "Fail fetching non-existent OCI chart", @@ -189,10 +190,9 @@ func TestPullCmd(t *testing.T) { wantError: true, }, { - name: "Fail fetching OCI chart without version specified", - args: fmt.Sprintf("oci://%s/u/ocitestuser/nosuchthing", ociSrv.RegistryURL), - wantErrorMsg: "Error: --version flag is explicitly required for OCI registries", - wantError: true, + name: "Fail fetching OCI chart without version specified", + args: fmt.Sprintf("oci://%s/u/ocitestuser/nosuchthing", ociSrv.RegistryURL), + wantError: true, }, { name: "Fetching OCI chart without version option specified", @@ -207,7 +207,7 @@ func TestPullCmd(t *testing.T) { { name: "Fail fetching OCI chart with version mismatch", args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.2.0 --version 0.1.0", ociSrv.RegistryURL), - wantErrorMsg: "Error: chart reference and version mismatch: 0.2.0 is not 0.1.0", + wantErrorMsg: "chart reference and version mismatch: 0.1.0 is not 0.2.0", wantError: true, }, } @@ -228,6 +228,9 @@ func TestPullCmd(t *testing.T) { // Create file or Dir before helm pull --untar, see: https://github.com/helm/helm/issues/7182 if tt.existFile != "" { file := filepath.Join(outdir, tt.existFile) + if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil { + t.Fatal(err) + } _, err := os.Create(file) if err != nil { t.Fatal(err) @@ -235,7 +238,7 @@ func TestPullCmd(t *testing.T) { } if tt.existDir != "" { file := filepath.Join(outdir, tt.existDir) - err := os.Mkdir(file, 0755) + err := os.MkdirAll(file, 0755) if err != nil { t.Fatal(err) } @@ -243,8 +246,8 @@ func TestPullCmd(t *testing.T) { _, out, err := executeActionCommand(cmd) if err != nil { if tt.wantError { - if tt.wantErrorMsg != "" && tt.wantErrorMsg == err.Error() { - t.Fatalf("Actual error %s, not equal to expected error %s", err, tt.wantErrorMsg) + if tt.wantErrorMsg != "" && tt.wantErrorMsg != err.Error() { + t.Fatalf("Actual error '%s', not equal to expected error '%s'", err, tt.wantErrorMsg) } return } @@ -303,16 +306,19 @@ func runPullTests(t *testing.T, tests []struct { } if tt.existDir != "" { file := filepath.Join(outdir, tt.existDir) - err := os.Mkdir(file, 0755) + err := os.MkdirAll(file, 0755) if err != nil { t.Fatal(err) } } _, _, err := executeActionCommand(cmd) + if tt.wantError && err == nil { + t.Fatalf("%q: expected error but got none", tt.name) + } if err != nil { if tt.wantError { - if tt.wantErrorMsg != "" && tt.wantErrorMsg == err.Error() { - t.Fatalf("Actual error %s, not equal to expected error %s", err, tt.wantErrorMsg) + if tt.wantErrorMsg != "" && tt.wantErrorMsg != err.Error() { + t.Fatalf("Actual error '%s', not equal to expected error '%s'", err, tt.wantErrorMsg) } return } @@ -487,10 +493,9 @@ func TestPullWithCredentialsCmdOCIRegistry(t *testing.T) { wantError: true, }, { - name: "Fail fetching OCI chart without version specified", - args: buildOCIURL(ociSrv.RegistryURL, "nosuchthing", "", ociSrv.TestUsername, ociSrv.TestPassword), - wantErrorMsg: "Error: --version flag is explicitly required for OCI registries", - wantError: true, + name: "Fail fetching OCI chart without version specified", + args: buildOCIURL(ociSrv.RegistryURL, "nosuchthing", "", ociSrv.TestUsername, ociSrv.TestPassword), + wantError: true, }, } diff --git a/pkg/cmd/testdata/testcharts/test-0.1.0.tgz b/pkg/cmd/testdata/testcharts/test-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..9ed772a7f924ffc4e280de8faa67ff88d857f9ca GIT binary patch literal 319 zcmV-F0l@wriwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PK~)O2jY_hI{T)1iW`A+a?QpRj+z*@ji5?8c5TUOpCs~Y($V; zdeC-J@?D0xbY|fH8m7Rz+gJ2ly<=~SH2?t6O%p2sq!nb{6jDj3Wv!Ju6d?CfHHCmx zwn>-*qc7mcv(n@K2soN&^1%%5C}vrnDYV{<9QzXm7VRY)q8q%J9HSTQ;5uts7MJ80 z_fk6S=@`3m{`wMBvV!v3w`G=z{?BM93akA9mi&i$pZedxA^X1tH{y;uoGgpa5Jc0X z%-7y5vjx$Arhc@@6m}1fr~9Xh-}pCYEc(CKiT|}$I{Uu`-`;rR0;9EbuCn`?OeXX1 R`3L|2|NmE1eCz-a007`FmKp#6 literal 0 HcmV?d00001 diff --git a/pkg/cmd/testdata/testcharts/test/Chart.yaml b/pkg/cmd/testdata/testcharts/test/Chart.yaml new file mode 100644 index 000000000..53e47c820 --- /dev/null +++ b/pkg/cmd/testdata/testcharts/test/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: Test chart for untar conflict testing +name: test +version: 0.1.0 diff --git a/pkg/cmd/testdata/testcharts/test/values.yaml b/pkg/cmd/testdata/testcharts/test/values.yaml new file mode 100644 index 000000000..2f01ba536 --- /dev/null +++ b/pkg/cmd/testdata/testcharts/test/values.yaml @@ -0,0 +1 @@ +# Default values for test diff --git a/pkg/cmd/testdata/testcharts/test1-0.1.0.tgz b/pkg/cmd/testdata/testcharts/test1-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..60e00324cd0c127c4449507407a2cbadf5d1cd5b GIT binary patch literal 327 zcmV-N0l5AjiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PK~)PJ}QJhB@;TO}w)+uwb%R^{NLq-Y4v!O-do{K=kbmpqEC& zL0RLb-=!Hy7$*6@j<6Tp-Mo;K?2THJYybd2wcpPt0E8!CQ6^X-6j)S%MFN=TIVK9W z_?eKw3%LY-+$$XJpFq8SV&`>d4{F5p#J+aAm$^Q&7gbNuP)+C5V1=BjJKGpti<*+7 z?Q?3W<-=TII(~i%GRz?T>)|4+EdGyV2KHNB0Bin3RfYcVt33LD4zAQKH#8VgO9p1s z=TLnACXHfW42%E5ZEr}}@9rOtcaNF8$}jd<^}h`LFRt@4`hO0l9giPNURy6@44<;E ZpGNN^kx1lncn<&o|Ns7?^pyY*006??l&Js! literal 0 HcmV?d00001 diff --git a/pkg/cmd/testdata/testcharts/test1/Chart.yaml b/pkg/cmd/testdata/testcharts/test1/Chart.yaml new file mode 100644 index 000000000..3dc8fbbf2 --- /dev/null +++ b/pkg/cmd/testdata/testcharts/test1/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: Test chart for untar conflict testing +name: test1 +version: 0.1.0 diff --git a/pkg/cmd/testdata/testcharts/test1/values.yaml b/pkg/cmd/testdata/testcharts/test1/values.yaml new file mode 100644 index 000000000..823016ffc --- /dev/null +++ b/pkg/cmd/testdata/testcharts/test1/values.yaml @@ -0,0 +1,2 @@ +# Default values for test1# Default values for test1 + From 2f1ecc7100868be90d302a299d707c70a7f45276 Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Mon, 13 Nov 2023 21:21:43 +0100 Subject: [PATCH 009/571] fix(uninstall): supersede deployed releases This ensures that when `helm uninstall` is run with `--keep-history` any release in a `deployed` state other than the last release (e.g. due to a failed upgrade) is being marked as `superseded`. As a by-effect, running `helm upgrade` on a release which has been uninstalled after an upgrade failure now no longer works. But instead fails with a `"" has no deployed releases` error. Which is the (likely) intended behavior, and prevents other side-effects like rolling back to a release version which happened before the uninstall if `--atomic` (or `--rollback-on-failure`) was provided. Signed-off-by: Hidde Beydals --- pkg/action/uninstall.go | 19 +++++++++++++++++++ ...ninstall-keep-history-earlier-deployed.txt | 1 + pkg/cmd/uninstall_test.go | 10 ++++++++++ 3 files changed, 30 insertions(+) create mode 100644 pkg/cmd/testdata/output/uninstall-keep-history-earlier-deployed.txt diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index 0cb31be43..0773ff222 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -188,6 +188,25 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error) u.cfg.Logger().Debug("uninstall: Failed to store updated release", slog.Any("error", err)) } + // Supersede all previous deployments, see issue #12556 (which is a + // variation on #2941). + deployed, err := u.cfg.Releases.DeployedAll(name) + if err != nil && !errors.Is(err, driver.ErrNoDeployedReleases) { + return nil, err + } + for _, reli := range deployed { + rel, err := releaserToV1Release(reli) + if err != nil { + return nil, err + } + + u.cfg.Logger().Debug("superseding previous deployment", "version", rel.Version) + rel.Info.Status = common.StatusSuperseded + if err := u.cfg.Releases.Update(rel); err != nil { + u.cfg.Logger().Debug("uninstall: Failed to store updated release", slog.Any("error", err)) + } + } + if len(errs) > 0 { return res, fmt.Errorf("uninstallation completed with %d error(s): %w", len(errs), joinErrors(errs, "; ")) } diff --git a/pkg/cmd/testdata/output/uninstall-keep-history-earlier-deployed.txt b/pkg/cmd/testdata/output/uninstall-keep-history-earlier-deployed.txt new file mode 100644 index 000000000..f5454b88d --- /dev/null +++ b/pkg/cmd/testdata/output/uninstall-keep-history-earlier-deployed.txt @@ -0,0 +1 @@ +release "aeneas" uninstalled diff --git a/pkg/cmd/uninstall_test.go b/pkg/cmd/uninstall_test.go index 1123f449b..ce436e68c 100644 --- a/pkg/cmd/uninstall_test.go +++ b/pkg/cmd/uninstall_test.go @@ -19,6 +19,7 @@ package cmd import ( "testing" + "helm.sh/helm/v4/pkg/release/common" release "helm.sh/helm/v4/pkg/release/v1" ) @@ -57,6 +58,15 @@ func TestUninstall(t *testing.T) { golden: "output/uninstall-keep-history.txt", rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "aeneas"})}, }, + { + name: "keep history with earlier deployed release", + cmd: "uninstall aeneas --keep-history", + golden: "output/uninstall-keep-history-earlier-deployed.txt", + rels: []*release.Release{ + release.Mock(&release.MockReleaseOptions{Name: "aeneas", Version: 1, Status: common.StatusDeployed}), + release.Mock(&release.MockReleaseOptions{Name: "aeneas", Version: 2, Status: common.StatusFailed}), + }, + }, { name: "wait", cmd: "uninstall aeneas --wait", From d158708fbfb08bc452d1733d9b8cc4c7dc9f8902 Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Mon, 13 Nov 2023 22:02:07 +0100 Subject: [PATCH 010/571] fix(rollback): `errors.Is` instead of string comp Signed-off-by: Hidde Beydals --- pkg/action/rollback.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index cd160dfec..4cdb2d33b 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -18,8 +18,8 @@ package action import ( "bytes" + "errors" "fmt" - "strings" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -28,6 +28,7 @@ import ( "helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/release/common" release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage/driver" ) // Rollback is the action for rolling back to a given release. @@ -278,7 +279,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas } deployed, err := r.cfg.Releases.DeployedAll(currentRelease.Name) - if err != nil && !strings.Contains(err.Error(), "has no deployed releases") { + if err != nil && !errors.Is(err, driver.ErrNoDeployedReleases) { return nil, err } // Supersede all previous deployments, see issue #2941. From c04e18e45253f08d127a37c8328e7084e486c7cc Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Wed, 12 Nov 2025 21:23:01 +0100 Subject: [PATCH 011/571] fix: improve plugin name validation error messages Signed-off-by: Benoit Tigeot --- internal/plugin/metadata_legacy.go | 5 ++- internal/plugin/metadata_legacy_test.go | 55 +++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 internal/plugin/metadata_legacy_test.go diff --git a/internal/plugin/metadata_legacy.go b/internal/plugin/metadata_legacy.go index a7b245dc0..8f74d44cb 100644 --- a/internal/plugin/metadata_legacy.go +++ b/internal/plugin/metadata_legacy.go @@ -69,7 +69,10 @@ type MetadataLegacy struct { func (m *MetadataLegacy) Validate() error { if !validPluginName.MatchString(m.Name) { - return fmt.Errorf("invalid plugin name") + if m.Name == "" { + return fmt.Errorf("plugin name is empty or missing: ensure plugin.yaml contains 'name:' field (lowercase)") + } + return fmt.Errorf("invalid plugin name %q: plugin names can only contain ASCII characters a-z, A-Z, 0-9, _ and -", m.Name) } m.Usage = sanitizeString(m.Usage) diff --git a/internal/plugin/metadata_legacy_test.go b/internal/plugin/metadata_legacy_test.go new file mode 100644 index 000000000..caabcc12a --- /dev/null +++ b/internal/plugin/metadata_legacy_test.go @@ -0,0 +1,55 @@ +/* +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 plugin + +import ( + "strings" + "testing" +) + +func TestMetadataLegacyValidate_EmptyName(t *testing.T) { + metadata := MetadataLegacy{ + Name: "", + Version: "1.0.0", + } + + err := metadata.Validate() + if err == nil { + t.Fatal("expected error for empty plugin name") + } + + expectedMsg := "plugin name is empty or missing" + if !strings.Contains(err.Error(), expectedMsg) { + t.Errorf("expected error to contain %q, got %q", expectedMsg, err.Error()) + } +} + +func TestMetadataLegacyValidate_InvalidName(t *testing.T) { + metadata := MetadataLegacy{ + Name: "invalid name", + Version: "1.0.0", + } + + err := metadata.Validate() + if err == nil { + t.Fatal("expected error for invalid plugin name") + } + + expectedMsg := "plugin names can only contain ASCII characters" + if !strings.Contains(err.Error(), expectedMsg) { + t.Errorf("expected error to contain %q, got %q", expectedMsg, err.Error()) + } +} From f4b139a82c4f5f3b38f5cb7b90de3c2e93d62dc3 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Fri, 14 Nov 2025 15:17:44 +0100 Subject: [PATCH 012/571] Make validation error similar and explicit for both metadatas Signed-off-by: Benoit Tigeot --- internal/plugin/metadata_legacy.go | 8 ++++---- internal/plugin/metadata_v1.go | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/internal/plugin/metadata_legacy.go b/internal/plugin/metadata_legacy.go index 8f74d44cb..562a0287e 100644 --- a/internal/plugin/metadata_legacy.go +++ b/internal/plugin/metadata_legacy.go @@ -68,11 +68,11 @@ type MetadataLegacy struct { } func (m *MetadataLegacy) Validate() error { + if m.Name == "" { + return fmt.Errorf("missing plugin name") + } if !validPluginName.MatchString(m.Name) { - if m.Name == "" { - return fmt.Errorf("plugin name is empty or missing: ensure plugin.yaml contains 'name:' field (lowercase)") - } - return fmt.Errorf("invalid plugin name %q: plugin names can only contain ASCII characters a-z, A-Z, 0-9, _ and -", m.Name) + return fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name) } m.Usage = sanitizeString(m.Usage) diff --git a/internal/plugin/metadata_v1.go b/internal/plugin/metadata_v1.go index 81dbc2e20..5e83bd352 100644 --- a/internal/plugin/metadata_v1.go +++ b/internal/plugin/metadata_v1.go @@ -47,8 +47,11 @@ type MetadataV1 struct { } func (m *MetadataV1) Validate() error { + if m.Name == "" { + return fmt.Errorf("missing plugin `name`") + } if !validPluginName.MatchString(m.Name) { - return fmt.Errorf("invalid plugin `name`") + return fmt.Errorf("invalid plugin `name` %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name) } if m.APIVersion != "v1" { From cf077ceb2758a63aa5f87410204d11c5f2ad3a7b Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Fri, 14 Nov 2025 15:33:05 +0100 Subject: [PATCH 013/571] fix: focus only on plugin name but give more info about what we get And improve test Follow Scott comment: > here I would have a list of valid and invalid names based on pattern, with a check and intended outcome for each one so that we comprehensively cover the rules. https://github.com/helm/helm/pull/31491/files#r2524820312 Signed-off-by: Benoit Tigeot --- internal/plugin/metadata_legacy.go | 3 -- internal/plugin/metadata_legacy_test.go | 63 ++++++++++++++----------- internal/plugin/metadata_v1.go | 5 +- 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/internal/plugin/metadata_legacy.go b/internal/plugin/metadata_legacy.go index 562a0287e..3cd1a50cd 100644 --- a/internal/plugin/metadata_legacy.go +++ b/internal/plugin/metadata_legacy.go @@ -68,9 +68,6 @@ type MetadataLegacy struct { } func (m *MetadataLegacy) Validate() error { - if m.Name == "" { - return fmt.Errorf("missing plugin name") - } if !validPluginName.MatchString(m.Name) { return fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name) } diff --git a/internal/plugin/metadata_legacy_test.go b/internal/plugin/metadata_legacy_test.go index caabcc12a..1ee23739c 100644 --- a/internal/plugin/metadata_legacy_test.go +++ b/internal/plugin/metadata_legacy_test.go @@ -16,40 +16,49 @@ limitations under the License. package plugin import ( - "strings" "testing" ) -func TestMetadataLegacyValidate_EmptyName(t *testing.T) { - metadata := MetadataLegacy{ - Name: "", - Version: "1.0.0", - } - - err := metadata.Validate() - if err == nil { - t.Fatal("expected error for empty plugin name") - } +func TestMetadataLegacyValidate_PluginName(t *testing.T) { + tests := []struct { + name string + pluginName string + shouldPass bool + }{ + // Valid names + {"lowercase", "myplugin", true}, + {"uppercase", "MYPLUGIN", true}, + {"mixed case", "MyPlugin", true}, + {"with digits", "plugin123", true}, + {"with hyphen", "my-plugin", true}, + {"with underscore", "my_plugin", true}, + {"mixed chars", "my-awesome_plugin_123", true}, - expectedMsg := "plugin name is empty or missing" - if !strings.Contains(err.Error(), expectedMsg) { - t.Errorf("expected error to contain %q, got %q", expectedMsg, err.Error()) + // Invalid names + {"empty", "", false}, + {"space", "my plugin", false}, + {"colon", "Name:", false}, + {"period", "my.plugin", false}, + {"slash", "my/plugin", false}, + {"dollar", "$plugin", false}, + {"unicode", "plügîn", false}, } -} -func TestMetadataLegacyValidate_InvalidName(t *testing.T) { - metadata := MetadataLegacy{ - Name: "invalid name", - Version: "1.0.0", - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + metadata := MetadataLegacy{ + Name: tt.pluginName, + Version: "1.0.0", + } - err := metadata.Validate() - if err == nil { - t.Fatal("expected error for invalid plugin name") - } + err := metadata.Validate() - expectedMsg := "plugin names can only contain ASCII characters" - if !strings.Contains(err.Error(), expectedMsg) { - t.Errorf("expected error to contain %q, got %q", expectedMsg, err.Error()) + if tt.shouldPass && err != nil { + t.Errorf("expected %q to be valid, got error: %v", tt.pluginName, err) + } + if !tt.shouldPass && err == nil { + t.Errorf("expected %q to be invalid, but passed", tt.pluginName) + } + }) } } diff --git a/internal/plugin/metadata_v1.go b/internal/plugin/metadata_v1.go index 5e83bd352..81dbc2e20 100644 --- a/internal/plugin/metadata_v1.go +++ b/internal/plugin/metadata_v1.go @@ -47,11 +47,8 @@ type MetadataV1 struct { } func (m *MetadataV1) Validate() error { - if m.Name == "" { - return fmt.Errorf("missing plugin `name`") - } if !validPluginName.MatchString(m.Name) { - return fmt.Errorf("invalid plugin `name` %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name) + return fmt.Errorf("invalid plugin `name`") } if m.APIVersion != "v1" { From 9e1e3d21c59820b39a05d950bf27027ba7ef1662 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Fri, 14 Nov 2025 15:36:21 +0100 Subject: [PATCH 014/571] fix: Make invalid name error message more similar and move tests Follow Scott comment\ https://github.com/helm/helm/pull/31491/files#r2524933784 Signed-off-by: Benoit Tigeot --- internal/plugin/metadata.go | 2 +- internal/plugin/metadata_legacy.go | 2 +- internal/plugin/metadata_legacy_test.go | 19 ++----------- internal/plugin/metadata_test.go | 4 +-- internal/plugin/plugin_test.go | 38 +++++++++++++++++++++++++ 5 files changed, 44 insertions(+), 21 deletions(-) diff --git a/internal/plugin/metadata.go b/internal/plugin/metadata.go index 111c0599f..15a954037 100644 --- a/internal/plugin/metadata.go +++ b/internal/plugin/metadata.go @@ -54,7 +54,7 @@ func (m Metadata) Validate() error { var errs []error if !validPluginName.MatchString(m.Name) { - errs = append(errs, fmt.Errorf("invalid name")) + errs = append(errs, fmt.Errorf("invalid name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name)) } if m.APIVersion == "" { diff --git a/internal/plugin/metadata_legacy.go b/internal/plugin/metadata_legacy.go index 3cd1a50cd..1b80e6196 100644 --- a/internal/plugin/metadata_legacy.go +++ b/internal/plugin/metadata_legacy.go @@ -69,7 +69,7 @@ type MetadataLegacy struct { func (m *MetadataLegacy) Validate() error { if !validPluginName.MatchString(m.Name) { - return fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name) + return fmt.Errorf("invalid name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name) } m.Usage = sanitizeString(m.Usage) diff --git a/internal/plugin/metadata_legacy_test.go b/internal/plugin/metadata_legacy_test.go index 1ee23739c..79d57211c 100644 --- a/internal/plugin/metadata_legacy_test.go +++ b/internal/plugin/metadata_legacy_test.go @@ -25,23 +25,8 @@ func TestMetadataLegacyValidate_PluginName(t *testing.T) { pluginName string shouldPass bool }{ - // Valid names - {"lowercase", "myplugin", true}, - {"uppercase", "MYPLUGIN", true}, - {"mixed case", "MyPlugin", true}, - {"with digits", "plugin123", true}, - {"with hyphen", "my-plugin", true}, - {"with underscore", "my_plugin", true}, - {"mixed chars", "my-awesome_plugin_123", true}, - - // Invalid names - {"empty", "", false}, - {"space", "my plugin", false}, - {"colon", "Name:", false}, - {"period", "my.plugin", false}, - {"slash", "my/plugin", false}, - {"dollar", "$plugin", false}, - {"unicode", "plügîn", false}, + {"valid name", "my-plugin", true}, + {"invalid with space", "my plugin", false}, } for _, tt := range tests { diff --git a/internal/plugin/metadata_test.go b/internal/plugin/metadata_test.go index 28bc4cf51..0c699b00f 100644 --- a/internal/plugin/metadata_test.go +++ b/internal/plugin/metadata_test.go @@ -66,8 +66,8 @@ func TestValidatePluginData(t *testing.T) { } else if !item.pass && err == nil { t.Errorf("expected case %d to fail", i) } - if !item.pass && err.Error() != item.errString { - t.Errorf("index [%d]: expected the following error: %s, but got: %s", i, item.errString, err.Error()) + if !item.pass && !strings.Contains(err.Error(), item.errString) { + t.Errorf("index [%d]: expected error to contain: %s, but got: %s", i, item.errString, err.Error()) } } } diff --git a/internal/plugin/plugin_test.go b/internal/plugin/plugin_test.go index b6c2245ff..d911942b9 100644 --- a/internal/plugin/plugin_test.go +++ b/internal/plugin/plugin_test.go @@ -21,6 +21,44 @@ import ( "helm.sh/helm/v4/internal/plugin/schema" ) +func TestValidPluginName(t *testing.T) { + tests := []struct { + name string + pluginName string + shouldPass bool + }{ + // Valid names + {"lowercase", "myplugin", true}, + {"uppercase", "MYPLUGIN", true}, + {"mixed case", "MyPlugin", true}, + {"with digits", "plugin123", true}, + {"with hyphen", "my-plugin", true}, + {"with underscore", "my_plugin", true}, + {"mixed chars", "my-awesome_plugin_123", true}, + + // Invalid names + {"empty", "", false}, + {"space", "my plugin", false}, + {"colon", "plugin:", false}, + {"period", "my.plugin", false}, + {"slash", "my/plugin", false}, + {"dollar", "$plugin", false}, + {"unicode", "plügîn", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + matches := validPluginName.MatchString(tt.pluginName) + if tt.shouldPass && !matches { + t.Errorf("expected %q to match validPluginName regex", tt.pluginName) + } + if !tt.shouldPass && matches { + t.Errorf("expected %q to not match validPluginName regex", tt.pluginName) + } + }) + } +} + func mockSubprocessCLIPlugin(t *testing.T, pluginName string) *SubprocessPluginRuntime { t.Helper() From acf331a0057ee79246f6058fca02313be29506fa Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Fri, 14 Nov 2025 16:10:22 +0100 Subject: [PATCH 015/571] fix: improve plugin name validation err messages early via unmarshalling - Add strict YAML unmarshalling for v1 plugins (d.KnownFields) - Add comprehensive test coverage for validPluginName regex - Maintain backwards compatibility for legacy plugins Signed-off-by: Benoit Tigeot --- internal/plugin/loader.go | 2 ++ internal/plugin/loader_test.go | 58 ++++++++++++++++++++++++++++++ internal/plugin/metadata.go | 2 +- internal/plugin/metadata_legacy.go | 2 +- internal/plugin/metadata_test.go | 10 +++--- 5 files changed, 67 insertions(+), 7 deletions(-) diff --git a/internal/plugin/loader.go b/internal/plugin/loader.go index a58a84126..2f051b99e 100644 --- a/internal/plugin/loader.go +++ b/internal/plugin/loader.go @@ -47,6 +47,7 @@ func loadMetadataLegacy(metadataData []byte) (*Metadata, error) { var ml MetadataLegacy d := yaml.NewDecoder(bytes.NewReader(metadataData)) + // NOTE: No strict unmarshalling for legacy plugins - maintain backwards compatibility if err := d.Decode(&ml); err != nil { return nil, err } @@ -66,6 +67,7 @@ func loadMetadataV1(metadataData []byte) (*Metadata, error) { var mv1 MetadataV1 d := yaml.NewDecoder(bytes.NewReader(metadataData)) + d.KnownFields(true) if err := d.Decode(&mv1); err != nil { return nil, err } diff --git a/internal/plugin/loader_test.go b/internal/plugin/loader_test.go index 47c214910..31fd8df78 100644 --- a/internal/plugin/loader_test.go +++ b/internal/plugin/loader_test.go @@ -268,3 +268,61 @@ func TestFindPlugins(t *testing.T) { }) } } + +func TestLoadMetadataLegacy_CapitalNameField(t *testing.T) { + yamlWithCapitalName := `Name: my-plugin +version: 1.0.0 +usage: test plugin +description: test description +command: echo test` + + _, err := loadMetadataLegacy([]byte(yamlWithCapitalName)) + + // Legacy plugins: No strict unmarshalling (backwards compatibility) + // YAML decoder silently ignores "Name:", then validation catches empty name + require.Error(t, err, "expected error for capital 'Name:' field") + assert.Contains(t, err.Error(), "invalid plugin name \"\": must contain only a-z, A-Z, 0-9, _ and -") + + t.Logf("Legacy error (validation catches empty name): %v", err) + t.Log("NOTE: V1 plugins use strict unmarshalling and would get: yaml: field Name not found") +} + +func TestLoadMetadataLegacy_CorrectField(t *testing.T) { + yamlWithCorrectName := `name: my-plugin +version: 1.0.0 +usage: test plugin +description: test description +command: echo test` + + m, err := loadMetadataLegacy([]byte(yamlWithCorrectName)) + + require.NoError(t, err, "expected success for correct 'name:' field") + assert.Equal(t, "my-plugin", m.Name) +} + +func TestLoadMetadataV1_CapitalNameField(t *testing.T) { + yamlWithCapitalName := `apiVersion: v1 +Name: my-plugin +type: cli/v1 +runtime: subprocess +` + + _, err := loadMetadataV1([]byte(yamlWithCapitalName)) + + require.Error(t, err, "expected error for capital 'Name:' field") + assert.Contains(t, err.Error(), "field Name not found in type plugin.MetadataV1") + + t.Logf("V1 error (strict unmarshalling): %v", err) +} + +func TestLoadMetadataV1_CorrectField(t *testing.T) { + yamlWithCorrectName := `apiVersion: v1 +name: my-plugin +type: cli/v1 +runtime: subprocess +` + m, err := loadMetadataV1([]byte(yamlWithCorrectName)) + + require.NoError(t, err, "expected success for correct 'name:' field") + assert.Equal(t, "my-plugin", m.Name) +} diff --git a/internal/plugin/metadata.go b/internal/plugin/metadata.go index 15a954037..b051c1223 100644 --- a/internal/plugin/metadata.go +++ b/internal/plugin/metadata.go @@ -54,7 +54,7 @@ func (m Metadata) Validate() error { var errs []error if !validPluginName.MatchString(m.Name) { - errs = append(errs, fmt.Errorf("invalid name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name)) + errs = append(errs, fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name)) } if m.APIVersion == "" { diff --git a/internal/plugin/metadata_legacy.go b/internal/plugin/metadata_legacy.go index 1b80e6196..3cd1a50cd 100644 --- a/internal/plugin/metadata_legacy.go +++ b/internal/plugin/metadata_legacy.go @@ -69,7 +69,7 @@ type MetadataLegacy struct { func (m *MetadataLegacy) Validate() error { if !validPluginName.MatchString(m.Name) { - return fmt.Errorf("invalid name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name) + return fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name) } m.Usage = sanitizeString(m.Usage) diff --git a/internal/plugin/metadata_test.go b/internal/plugin/metadata_test.go index 0c699b00f..145ef5101 100644 --- a/internal/plugin/metadata_test.go +++ b/internal/plugin/metadata_test.go @@ -53,10 +53,10 @@ func TestValidatePluginData(t *testing.T) { }{ {true, mockSubprocessCLIPlugin(t, "abcdefghijklmnopqrstuvwxyz0123456789_-ABC"), ""}, {true, mockSubprocessCLIPlugin(t, "foo-bar-FOO-BAR_1234"), ""}, - {false, mockSubprocessCLIPlugin(t, "foo -bar"), "invalid name"}, - {false, mockSubprocessCLIPlugin(t, "$foo -bar"), "invalid name"}, // Test leading chars - {false, mockSubprocessCLIPlugin(t, "foo -bar "), "invalid name"}, // Test trailing chars - {false, mockSubprocessCLIPlugin(t, "foo\nbar"), "invalid name"}, // Test newline + {false, mockSubprocessCLIPlugin(t, "foo -bar"), "invalid plugin name"}, + {false, mockSubprocessCLIPlugin(t, "$foo -bar"), "invalid plugin name"}, // Test leading chars + {false, mockSubprocessCLIPlugin(t, "foo -bar "), "invalid plugin name"}, // Test trailing chars + {false, mockSubprocessCLIPlugin(t, "foo\nbar"), "invalid plugin name"}, // Test newline {true, mockNoCommand, ""}, // Test no command metadata works {true, mockLegacyCommand, ""}, // Test legacy command metadata works } { @@ -92,7 +92,7 @@ func TestMetadataValidateMultipleErrors(t *testing.T) { // Check that all expected errors are present in the joined error expectedErrors := []string{ - "invalid name", + "invalid plugin name", "empty APIVersion", "empty type field", "empty runtime field", From 2d49f0cb4ab1996bc203c9a91454021a375e522c Mon Sep 17 00:00:00 2001 From: yxxhero Date: Sat, 15 Nov 2025 11:16:08 +0800 Subject: [PATCH 016/571] Refactor environment variable expansion in PrepareCommands and update tests Signed-off-by: yxxhero --- internal/plugin/subprocess_commands.go | 11 ++++++----- internal/plugin/subprocess_commands_test.go | 13 ++++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/internal/plugin/subprocess_commands.go b/internal/plugin/subprocess_commands.go index e21ec2bab..9a57ed891 100644 --- a/internal/plugin/subprocess_commands.go +++ b/internal/plugin/subprocess_commands.go @@ -82,15 +82,16 @@ func PrepareCommands(cmds []PlatformCommand, expandArgs bool, extraArgs []string if len(cmdParts) == 0 || cmdParts[0] == "" { return "", nil, fmt.Errorf("no plugin command is applicable") } - - main := os.Expand(cmdParts[0], func(key string) string { + envMappingFunc := func(key string) string { return env[key] - }) + } + + main := os.Expand(cmdParts[0], envMappingFunc) baseArgs := []string{} if len(cmdParts) > 1 { for _, cmdPart := range cmdParts[1:] { if expandArgs { - baseArgs = append(baseArgs, os.ExpandEnv(cmdPart)) + baseArgs = append(baseArgs, os.Expand(cmdPart, envMappingFunc)) } else { baseArgs = append(baseArgs, cmdPart) } @@ -99,7 +100,7 @@ func PrepareCommands(cmds []PlatformCommand, expandArgs bool, extraArgs []string for _, arg := range args { if expandArgs { - baseArgs = append(baseArgs, os.ExpandEnv(arg)) + baseArgs = append(baseArgs, os.Expand(arg, envMappingFunc)) } else { baseArgs = append(baseArgs, arg) } diff --git a/internal/plugin/subprocess_commands_test.go b/internal/plugin/subprocess_commands_test.go index c1eba7a55..a916a176f 100644 --- a/internal/plugin/subprocess_commands_test.go +++ b/internal/plugin/subprocess_commands_test.go @@ -16,6 +16,7 @@ limitations under the License. package plugin import ( + "os" "reflect" "runtime" "testing" @@ -224,16 +225,18 @@ func TestPrepareCommandsNoCommands(t *testing.T) { } func TestPrepareCommandsExpand(t *testing.T) { - t.Setenv("TEST", "test") + t.Setenv("TESTX", "testx") cmdMain := "sh" - cmdArgs := []string{"-c", "echo \"${TEST}\""} + cmdArgs := []string{"-c", "echo \"${TESTX}${TESTY}\""} cmds := []PlatformCommand{ {OperatingSystem: "", Architecture: "", Command: cmdMain, Args: cmdArgs}, } - expectedArgs := []string{"-c", "echo \"test\""} + expectedArgs := []string{"-c", "echo \"testxtesty\""} + + env := parseEnv(os.Environ()) + env["TESTY"] = "testy" - env := map[string]string{} cmd, args, err := PrepareCommands(cmds, true, []string{}, env) if err != nil { t.Fatal(err) @@ -253,8 +256,8 @@ func TestPrepareCommandsNoExpand(t *testing.T) { cmds := []PlatformCommand{ {OperatingSystem: "", Architecture: "", Command: cmdMain, Args: cmdArgs}, } + env := parseEnv(os.Environ()) - env := map[string]string{} cmd, args, err := PrepareCommands(cmds, false, []string{}, env) if err != nil { t.Fatal(err) From 855ebb688444eb45ab7a4964be8483765f6dbe13 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Fri, 14 Nov 2025 23:10:53 -0500 Subject: [PATCH 017/571] Copy adopted resource info Signed-off-by: George Jenkins --- pkg/action/validate.go | 6 ++++-- pkg/action/validate_test.go | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/action/validate.go b/pkg/action/validate.go index 761ccba47..1bef5a742 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -55,7 +55,8 @@ func requireAdoption(resources kube.ResourceList) (kube.ResourceList, error) { return fmt.Errorf("could not get information about the resource %s: %w", resourceString(info), err) } - requireUpdate.Append(info) + infoCopy := *info + requireUpdate.Append(&infoCopy) return nil }) @@ -84,7 +85,8 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN return fmt.Errorf("%s exists and cannot be imported into the current release: %s", resourceString(info), err) } - requireUpdate.Append(info) + infoCopy := *info + requireUpdate.Append(&infoCopy) return nil }) diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go index 3efecd6ff..879a5fa4f 100644 --- a/pkg/action/validate_test.go +++ b/pkg/action/validate_test.go @@ -132,6 +132,7 @@ func TestRequireAdoption(t *testing.T) { assert.NoError(t, err) assert.Len(t, found, 1) assert.Equal(t, found[0], existing) + assert.NotSame(t, found[0], existing) } func TestExistingResourceConflict(t *testing.T) { @@ -156,6 +157,7 @@ func TestExistingResourceConflict(t *testing.T) { assert.NoError(t, err) assert.Len(t, found, 1) assert.Equal(t, found[0], existing) + assert.NotSame(t, found[0], existing) // Verify that an existing resource that lacks labels/annotations results in an error resources = append(resources, conflict) From 18616e6ce969cd7fbbd8a85d77557402839d59db Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Sat, 15 Nov 2025 20:35:44 -0500 Subject: [PATCH 018/571] fix: Use server-side apply for object create during update Signed-off-by: George Jenkins --- pkg/action/install.go | 1 + pkg/kube/client.go | 77 ++++++++++++++++++++++++++----------------- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index 9008c06ec..2f5910284 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -419,6 +419,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st if err != nil { return nil, err } + if _, err := i.cfg.KubeClient.Create( resourceList, kube.ClientCreateOptionServerSideApply(i.ServerSideApply, false)); err != nil && !apierrors.IsAlreadyExists(err) { diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 68f1e6475..75c9006ea 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -114,6 +114,9 @@ const ( FieldValidationDirectiveStrict FieldValidationDirective = "Strict" ) +type CreateApplyFunc func(target *resource.Info) error +type UpdateApplyFunc func(original, target *resource.Info) error + func init() { // Add CRDs to the scheme. They are missing by default. if err := apiextv1.AddToScheme(scheme.Scheme); err != nil { @@ -268,6 +271,36 @@ func ClientCreateOptionFieldValidationDirective(fieldValidationDirective FieldVa } } +func (c *Client) makeCreateApplyFunc(serverSideApply, forceConflicts, dryRun bool, fieldValidationDirective FieldValidationDirective) CreateApplyFunc { + if serverSideApply { + c.Logger().Debug( + "using server-side apply for resource creation", + slog.Bool("forceConflicts", forceConflicts), + slog.Bool("dryRun", dryRun), + slog.String("fieldValidationDirective", string(fieldValidationDirective))) + + return func(target *resource.Info) error { + err := patchResourceServerSide(target, dryRun, forceConflicts, fieldValidationDirective) + + logger := c.Logger().With( + slog.String("namespace", target.Namespace), + slog.String("name", target.Name), + slog.String("gvk", target.Mapping.GroupVersionKind.String())) + if err != nil { + logger.Debug("Error patching resource", slog.Any("error", err)) + return err + } + + logger.Debug("Patched resource") + + return nil + } + } + + c.Logger().Debug("using client-side apply for resource creation") + return createResource +} + // Create creates Kubernetes resources specified in the resource list. func (c *Client) Create(resources ResourceList, options ...ClientCreateOption) (*Result, error) { c.Logger().Debug("creating resource(s)", "resources", len(resources)) @@ -285,32 +318,12 @@ func (c *Client) Create(resources ResourceList, options ...ClientCreateOption) ( return nil, fmt.Errorf("invalid client create option(s): %w", err) } - makeCreateApplyFunc := func() func(target *resource.Info) error { - if createOptions.serverSideApply { - c.Logger().Debug("using server-side apply for resource creation", slog.Bool("forceConflicts", createOptions.forceConflicts), slog.Bool("dryRun", createOptions.dryRun), slog.String("fieldValidationDirective", string(createOptions.fieldValidationDirective))) - return func(target *resource.Info) error { - err := patchResourceServerSide(target, createOptions.dryRun, createOptions.forceConflicts, createOptions.fieldValidationDirective) - - logger := c.Logger().With( - slog.String("namespace", target.Namespace), - slog.String("name", target.Name), - slog.String("gvk", target.Mapping.GroupVersionKind.String())) - if err != nil { - logger.Debug("Error patching resource", slog.Any("error", err)) - return err - } - - logger.Debug("Patched resource") - - return nil - } - } - - c.Logger().Debug("using client-side apply for resource creation") - return createResource - } - - if err := perform(resources, makeCreateApplyFunc()); err != nil { + createApplyFunc := c.makeCreateApplyFunc( + createOptions.serverSideApply, + createOptions.forceConflicts, + createOptions.dryRun, + createOptions.fieldValidationDirective) + if err := perform(resources, createApplyFunc); err != nil { return nil, err } return &Result{Created: resources}, nil @@ -512,7 +525,7 @@ func (c *Client) BuildTable(reader io.Reader, validate bool) (ResourceList, erro transformRequests) } -func (c *Client) update(originals, targets ResourceList, updateApplyFunc UpdateApplyFunc) (*Result, error) { +func (c *Client) update(originals, targets ResourceList, createApplyFunc CreateApplyFunc, updateApplyFunc UpdateApplyFunc) (*Result, error) { updateErrors := []error{} res := &Result{} @@ -686,8 +699,6 @@ func ClientUpdateOptionUpgradeClientSideFieldManager(upgradeClientSideFieldManag } } -type UpdateApplyFunc func(original, target *resource.Info) error - // Update takes the current list of objects and target list of objects and // creates resources that don't already exist, updates resources that have been // modified in the target configuration, and deletes resources from the current @@ -723,6 +734,12 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate return &Result{}, fmt.Errorf("invalid operation: cannot use server-side apply and force replace together") } + createApplyFunc := c.makeCreateApplyFunc( + updateOptions.serverSideApply, + updateOptions.forceConflicts, + updateOptions.dryRun, + updateOptions.fieldValidationDirective) + makeUpdateApplyFunc := func() UpdateApplyFunc { if updateOptions.forceReplace { c.Logger().Debug( @@ -783,7 +800,7 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate } } - return c.update(originals, targets, makeUpdateApplyFunc()) + return c.update(originals, targets, createApplyFunc, makeUpdateApplyFunc()) } // Delete deletes Kubernetes resources specified in the resources list with From 8c870248602e231be274b68b4cfd0f4b8387aa53 Mon Sep 17 00:00:00 2001 From: yxxhero Date: Sun, 16 Nov 2025 10:05:27 +0800 Subject: [PATCH 019/571] update tests Signed-off-by: yxxhero --- internal/plugin/subprocess_commands_test.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/internal/plugin/subprocess_commands_test.go b/internal/plugin/subprocess_commands_test.go index a916a176f..8e9c1663e 100644 --- a/internal/plugin/subprocess_commands_test.go +++ b/internal/plugin/subprocess_commands_test.go @@ -16,7 +16,6 @@ limitations under the License. package plugin import ( - "os" "reflect" "runtime" "testing" @@ -225,7 +224,6 @@ func TestPrepareCommandsNoCommands(t *testing.T) { } func TestPrepareCommandsExpand(t *testing.T) { - t.Setenv("TESTX", "testx") cmdMain := "sh" cmdArgs := []string{"-c", "echo \"${TESTX}${TESTY}\""} cmds := []PlatformCommand{ @@ -234,8 +232,10 @@ func TestPrepareCommandsExpand(t *testing.T) { expectedArgs := []string{"-c", "echo \"testxtesty\""} - env := parseEnv(os.Environ()) - env["TESTY"] = "testy" + env := map[string]string{ + "TESTX": "testx", + "TESTY": "testy", + } cmd, args, err := PrepareCommands(cmds, true, []string{}, env) if err != nil { @@ -250,13 +250,15 @@ func TestPrepareCommandsExpand(t *testing.T) { } func TestPrepareCommandsNoExpand(t *testing.T) { - t.Setenv("TEST", "test") cmdMain := "sh" cmdArgs := []string{"-c", "echo \"${TEST}\""} cmds := []PlatformCommand{ {OperatingSystem: "", Architecture: "", Command: cmdMain, Args: cmdArgs}, } - env := parseEnv(os.Environ()) + + env := map[string]string{ + "TEST": "test", + } cmd, args, err := PrepareCommands(cmds, false, []string{}, env) if err != nil { From c81a09b89abcda9905e41376585c842870cafc2c Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Sun, 16 Nov 2025 11:35:25 +0100 Subject: [PATCH 020/571] test: refactor TestMetadataLegacyValidate to be more generic Signed-off-by: Benoit Tigeot --- internal/plugin/metadata_legacy_test.go | 123 +++++++++++++++++++----- 1 file changed, 100 insertions(+), 23 deletions(-) diff --git a/internal/plugin/metadata_legacy_test.go b/internal/plugin/metadata_legacy_test.go index 79d57211c..9421e98b5 100644 --- a/internal/plugin/metadata_legacy_test.go +++ b/internal/plugin/metadata_legacy_test.go @@ -17,33 +17,110 @@ package plugin import ( "testing" + + "github.com/stretchr/testify/assert" ) -func TestMetadataLegacyValidate_PluginName(t *testing.T) { - tests := []struct { - name string - pluginName string - shouldPass bool - }{ - {"valid name", "my-plugin", true}, - {"invalid with space", "my plugin", false}, +func TestMetadataLegacyValidate(t *testing.T) { + testsValid := map[string]MetadataLegacy{ + "valid metadata": { + Name: "myplugin", + }, + "valid with command": { + Name: "myplugin", + Command: "echo hello", + }, + "valid with platformCommand": { + Name: "myplugin", + PlatformCommand: []PlatformCommand{ + {OperatingSystem: "linux", Architecture: "amd64", Command: "echo hello"}, + }, + }, + "valid with hooks": { + Name: "myplugin", + Hooks: Hooks{ + "install": "echo install", + }, + }, + "valid with platformHooks": { + Name: "myplugin", + PlatformHooks: PlatformHooks{ + "install": []PlatformCommand{ + {OperatingSystem: "linux", Architecture: "amd64", Command: "echo install"}, + }, + }, + }, + "valid with downloaders": { + Name: "myplugin", + Downloaders: []Downloaders{ + { + Protocols: []string{"myproto"}, + Command: "echo download", + }, + }, + }, + } + + for testName, metadata := range testsValid { + t.Run(testName, func(t *testing.T) { + assert.NoError(t, metadata.Validate()) + }) + } + + testsInvalid := map[string]MetadataLegacy{ + "invalid name": { + Name: "my plugin", // further tested in TestValidPluginName + }, + "both command and platformCommand": { + Name: "myplugin", + Command: "echo hello", + PlatformCommand: []PlatformCommand{ + {OperatingSystem: "linux", Architecture: "amd64", Command: "echo hello"}, + }, + }, + "both hooks and platformHooks": { + Name: "myplugin", + Hooks: Hooks{ + "install": "echo install", + }, + PlatformHooks: PlatformHooks{ + "install": []PlatformCommand{ + {OperatingSystem: "linux", Architecture: "amd64", Command: "echo install"}, + }, + }, + }, + "downloader with empty command": { + Name: "myplugin", + Downloaders: []Downloaders{ + { + Protocols: []string{"myproto"}, + Command: "", + }, + }, + }, + "downloader with no protocols": { + Name: "myplugin", + Downloaders: []Downloaders{ + { + Protocols: []string{}, + Command: "echo download", + }, + }, + }, + "downloader with empty protocol": { + Name: "myplugin", + Downloaders: []Downloaders{ + { + Protocols: []string{""}, + Command: "echo download", + }, + }, + }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - metadata := MetadataLegacy{ - Name: tt.pluginName, - Version: "1.0.0", - } - - err := metadata.Validate() - - if tt.shouldPass && err != nil { - t.Errorf("expected %q to be valid, got error: %v", tt.pluginName, err) - } - if !tt.shouldPass && err == nil { - t.Errorf("expected %q to be invalid, but passed", tt.pluginName) - } + for testName, metadata := range testsInvalid { + t.Run(testName, func(t *testing.T) { + assert.Error(t, metadata.Validate()) }) } } From 9b242dd9ed656438eedaf93e67003231d23dc765 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Sun, 16 Nov 2025 11:44:49 +0100 Subject: [PATCH 021/571] test: convert tests to table drive tests Signed-off-by: Benoit Tigeot --- internal/plugin/loader_test.go | 115 +++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 40 deletions(-) diff --git a/internal/plugin/loader_test.go b/internal/plugin/loader_test.go index 31fd8df78..e84905248 100644 --- a/internal/plugin/loader_test.go +++ b/internal/plugin/loader_test.go @@ -269,60 +269,95 @@ func TestFindPlugins(t *testing.T) { } } -func TestLoadMetadataLegacy_CapitalNameField(t *testing.T) { - yamlWithCapitalName := `Name: my-plugin +func TestLoadMetadataLegacy(t *testing.T) { + testCases := map[string]struct { + yaml string + expectError bool + errorContains string + expectedName string + logNote string + }{ + "capital name field": { + yaml: `Name: my-plugin version: 1.0.0 usage: test plugin description: test description -command: echo test` - - _, err := loadMetadataLegacy([]byte(yamlWithCapitalName)) - - // Legacy plugins: No strict unmarshalling (backwards compatibility) - // YAML decoder silently ignores "Name:", then validation catches empty name - require.Error(t, err, "expected error for capital 'Name:' field") - assert.Contains(t, err.Error(), "invalid plugin name \"\": must contain only a-z, A-Z, 0-9, _ and -") - - t.Logf("Legacy error (validation catches empty name): %v", err) - t.Log("NOTE: V1 plugins use strict unmarshalling and would get: yaml: field Name not found") -} - -func TestLoadMetadataLegacy_CorrectField(t *testing.T) { - yamlWithCorrectName := `name: my-plugin +command: echo test`, + expectError: true, + errorContains: `invalid plugin name "": must contain only a-z, A-Z, 0-9, _ and -`, + // Legacy plugins: No strict unmarshalling (backwards compatibility) + // YAML decoder silently ignores "Name:", then validation catches empty name + logNote: "NOTE: V1 plugins use strict unmarshalling and would get: yaml: field Name not found", + }, + "correct name field": { + yaml: `name: my-plugin version: 1.0.0 usage: test plugin description: test description -command: echo test` - - m, err := loadMetadataLegacy([]byte(yamlWithCorrectName)) +command: echo test`, + expectError: false, + expectedName: "my-plugin", + }, + } - require.NoError(t, err, "expected success for correct 'name:' field") - assert.Equal(t, "my-plugin", m.Name) + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + m, err := loadMetadataLegacy([]byte(tc.yaml)) + + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errorContains) + t.Logf("Legacy error (validation catches empty name): %v", err) + if tc.logNote != "" { + t.Log(tc.logNote) + } + } else { + require.NoError(t, err) + assert.Equal(t, tc.expectedName, m.Name) + } + }) + } } -func TestLoadMetadataV1_CapitalNameField(t *testing.T) { - yamlWithCapitalName := `apiVersion: v1 +func TestLoadMetadataV1(t *testing.T) { + testCases := map[string]struct { + yaml string + expectError bool + errorContains string + expectedName string + }{ + "capital name field": { + yaml: `apiVersion: v1 Name: my-plugin type: cli/v1 runtime: subprocess -` - - _, err := loadMetadataV1([]byte(yamlWithCapitalName)) - - require.Error(t, err, "expected error for capital 'Name:' field") - assert.Contains(t, err.Error(), "field Name not found in type plugin.MetadataV1") - - t.Logf("V1 error (strict unmarshalling): %v", err) -} - -func TestLoadMetadataV1_CorrectField(t *testing.T) { - yamlWithCorrectName := `apiVersion: v1 +`, + expectError: true, + errorContains: "field Name not found in type plugin.MetadataV1", + }, + "correct name field": { + yaml: `apiVersion: v1 name: my-plugin type: cli/v1 runtime: subprocess -` - m, err := loadMetadataV1([]byte(yamlWithCorrectName)) +`, + expectError: false, + expectedName: "my-plugin", + }, + } - require.NoError(t, err, "expected success for correct 'name:' field") - assert.Equal(t, "my-plugin", m.Name) + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + m, err := loadMetadataV1([]byte(tc.yaml)) + + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.errorContains) + t.Logf("V1 error (strict unmarshalling): %v", err) + } else { + require.NoError(t, err) + assert.Equal(t, tc.expectedName, m.Name) + } + }) + } } From b296cbef6c8ad43de4f921c680c8df77fdd1cc84 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Sun, 16 Nov 2025 11:49:08 +0100 Subject: [PATCH 022/571] test: split tests between valid and invalid Signed-off-by: Benoit Tigeot --- internal/plugin/plugin_test.go | 58 +++++++++++++++++----------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/internal/plugin/plugin_test.go b/internal/plugin/plugin_test.go index d911942b9..d71cec2e9 100644 --- a/internal/plugin/plugin_test.go +++ b/internal/plugin/plugin_test.go @@ -22,38 +22,38 @@ import ( ) func TestValidPluginName(t *testing.T) { - tests := []struct { - name string - pluginName string - shouldPass bool - }{ - // Valid names - {"lowercase", "myplugin", true}, - {"uppercase", "MYPLUGIN", true}, - {"mixed case", "MyPlugin", true}, - {"with digits", "plugin123", true}, - {"with hyphen", "my-plugin", true}, - {"with underscore", "my_plugin", true}, - {"mixed chars", "my-awesome_plugin_123", true}, - - // Invalid names - {"empty", "", false}, - {"space", "my plugin", false}, - {"colon", "plugin:", false}, - {"period", "my.plugin", false}, - {"slash", "my/plugin", false}, - {"dollar", "$plugin", false}, - {"unicode", "plügîn", false}, + validNames := map[string]string{ + "lowercase": "myplugin", + "uppercase": "MYPLUGIN", + "mixed case": "MyPlugin", + "with digits": "plugin123", + "with hyphen": "my-plugin", + "with underscore": "my_plugin", + "mixed chars": "my-awesome_plugin_123", } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - matches := validPluginName.MatchString(tt.pluginName) - if tt.shouldPass && !matches { - t.Errorf("expected %q to match validPluginName regex", tt.pluginName) + for name, pluginName := range validNames { + t.Run("valid/"+name, func(t *testing.T) { + if !validPluginName.MatchString(pluginName) { + t.Errorf("expected %q to match validPluginName regex", pluginName) } - if !tt.shouldPass && matches { - t.Errorf("expected %q to not match validPluginName regex", tt.pluginName) + }) + } + + invalidNames := map[string]string{ + "empty": "", + "space": "my plugin", + "colon": "plugin:", + "period": "my.plugin", + "slash": "my/plugin", + "dollar": "$plugin", + "unicode": "plügîn", + } + + for name, pluginName := range invalidNames { + t.Run("invalid/"+name, func(t *testing.T) { + if validPluginName.MatchString(pluginName) { + t.Errorf("expected %q to not match validPluginName regex", pluginName) } }) } From 71591ee63e5351522cd833ec4980428a91f634ea Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Sun, 16 Nov 2025 12:08:30 +0100 Subject: [PATCH 023/571] style: linting Signed-off-by: Benoit Tigeot --- internal/plugin/plugin_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/plugin/plugin_test.go b/internal/plugin/plugin_test.go index d71cec2e9..ae0b343f3 100644 --- a/internal/plugin/plugin_test.go +++ b/internal/plugin/plugin_test.go @@ -23,13 +23,13 @@ import ( func TestValidPluginName(t *testing.T) { validNames := map[string]string{ - "lowercase": "myplugin", - "uppercase": "MYPLUGIN", - "mixed case": "MyPlugin", - "with digits": "plugin123", - "with hyphen": "my-plugin", + "lowercase": "myplugin", + "uppercase": "MYPLUGIN", + "mixed case": "MyPlugin", + "with digits": "plugin123", + "with hyphen": "my-plugin", "with underscore": "my_plugin", - "mixed chars": "my-awesome_plugin_123", + "mixed chars": "my-awesome_plugin_123", } for name, pluginName := range validNames { From 5f3c617940081564a6bbabe9553996ae352d16ff Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sun, 16 Nov 2025 11:10:54 -0700 Subject: [PATCH 024/571] chore: add warning for registry login with namespace Signed-off-by: Terry Howe --- pkg/registry/client.go | 14 +++++++++++ pkg/registry/client_test.go | 46 +++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 9eb189216..750bb9715 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -24,6 +24,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "net/url" "os" @@ -224,12 +225,25 @@ type ( } ) +// warnIfHostHasPath checks if the host contains a repository path and logs a warning if it does. +// Returns true if the host contains a path component (i.e., contains a '/'). +func warnIfHostHasPath(host string) bool { + if strings.Contains(host, "/") { + registryHost := strings.Split(host, "/")[0] + slog.Warn("registry login currently only supports registry hostname, not a repository path", "host", host, "suggested", registryHost) + return true + } + return false +} + // Login logs into a registry func (c *Client) Login(host string, options ...LoginOption) error { for _, option := range options { option(&loginOperation{host, c}) } + warnIfHostHasPath(host) + reg, err := remote.NewRegistry(host) if err != nil { return err diff --git a/pkg/registry/client_test.go b/pkg/registry/client_test.go index 6ae32e342..98a8b2ea3 100644 --- a/pkg/registry/client_test.go +++ b/pkg/registry/client_test.go @@ -120,3 +120,49 @@ func TestLogin_ResetsForceAttemptOAuth2_OnFailure(t *testing.T) { t.Errorf("ForceAttemptOAuth2 should be false after failed Login") } } + +// TestWarnIfHostHasPath verifies that warnIfHostHasPath correctly detects path components. +func TestWarnIfHostHasPath(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + host string + wantWarn bool + }{ + { + name: "domain only", + host: "ghcr.io", + wantWarn: false, + }, + { + name: "domain with port", + host: "localhost:8000", + wantWarn: false, + }, + { + name: "domain with repository path", + host: "ghcr.io/terryhowe", + wantWarn: true, + }, + { + name: "domain with nested path", + host: "ghcr.io/terryhowe/myrepo", + wantWarn: true, + }, + { + name: "localhost with port and path", + host: "localhost:8000/myrepo", + wantWarn: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := warnIfHostHasPath(tt.host) + if got != tt.wantWarn { + t.Errorf("warnIfHostHasPath(%q) = %v, want %v", tt.host, got, tt.wantWarn) + } + }) + } +} From b1a976073f11a972b3f5a6860bc5647a79268ef5 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Mon, 17 Nov 2025 08:02:46 -0800 Subject: [PATCH 025/571] fix Signed-off-by: George Jenkins --- pkg/kube/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 75c9006ea..7e3db7a2f 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -545,7 +545,7 @@ func (c *Client) update(originals, targets ResourceList, createApplyFunc CreateA res.Created = append(res.Created, target) // Since the resource does not exist, create it. - if err := createResource(target); err != nil { + if err := createApplyFunc(target); err != nil { return fmt.Errorf("failed to create resource: %w", err) } From a9cdc781160f0469189b23769a99722d914c5858 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Mon, 17 Nov 2025 08:08:23 -0800 Subject: [PATCH 026/571] logs Signed-off-by: George Jenkins --- pkg/kube/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 7e3db7a2f..3b81ab862 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -287,11 +287,11 @@ func (c *Client) makeCreateApplyFunc(serverSideApply, forceConflicts, dryRun boo slog.String("name", target.Name), slog.String("gvk", target.Mapping.GroupVersionKind.String())) if err != nil { - logger.Debug("Error patching resource", slog.Any("error", err)) + logger.Debug("Error creating resource via patch", slog.Any("error", err)) return err } - logger.Debug("Patched resource") + logger.Debug("Created resource via patch") return nil } From f8a49f185218da62f58af2265db13ccc84a480b3 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Mon, 17 Nov 2025 08:31:56 -0800 Subject: [PATCH 027/571] fixup test Signed-off-by: George Jenkins --- pkg/kube/client_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index d49e179e0..f3a797246 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -350,9 +350,7 @@ func TestUpdate(t *testing.T) { "/namespaces/default/pods/otter:GET", "/namespaces/default/pods/otter:PATCH", "/namespaces/default/pods/dolphin:GET", - "/namespaces/default/pods:POST", // create dolphin - "/namespaces/default/pods:POST", // retry due to 409 - "/namespaces/default/pods:POST", // retry due to 409 + "/namespaces/default/pods/dolphin:PATCH", // create dolphin "/namespaces/default/pods/squid:GET", "/namespaces/default/pods/squid:DELETE", "/namespaces/default/pods/notfound:GET", @@ -464,6 +462,8 @@ func TestUpdate(t *testing.T) { return newResponseJSON(http.StatusConflict, resourceQuotaConflict) } + return newResponse(http.StatusOK, &listTarget.Items[1]) + case p == "/namespaces/default/pods/dolphin" && m == http.MethodPatch: return newResponse(http.StatusOK, &listTarget.Items[1]) case p == "/namespaces/default/pods/squid" && m == http.MethodDelete: return newResponse(http.StatusOK, &listTarget.Items[1]) @@ -485,10 +485,9 @@ func TestUpdate(t *testing.T) { Reason: metav1.StatusReasonForbidden, Code: http.StatusForbidden, }) - default: } - t.Fail() + t.FailNow() return nil, nil } From f6ceae9b7460e9a86a6ad99b63694cc30340b888 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 21:08:11 +0000 Subject: [PATCH 028/571] chore(deps): bump actions/checkout from 5.0.0 to 5.0.1 Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/08c6903cd8c0fde910a37f88322edcfb5dd907a8...93cb6efe18208431cddfb8368fd83d5badbf9bfd) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 5.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index dbd885350..b43b41d39 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # pin@v5.0.1 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1606902c7..c7ff9e20a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -43,7 +43,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # pin@v5.0.1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index ff441aad5..a14e4125b 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # pin@v5.0.1 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 84d260a8f..db57ed4b3 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # pin@v5.0.1 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5d547ad6..9bbe0eeef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest-16-cores steps: - name: Checkout source code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # pin@v5.0.1 with: fetch-depth: 0 @@ -84,7 +84,7 @@ jobs: if: github.ref == 'refs/heads/main' && github.repository == 'helm/helm' steps: - name: Checkout source code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # pin@v5.0.1 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 4b836a33c..0dbd24766 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -28,7 +28,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false From ce273eea48c8e42323c648527bb8e0d0dbf6fd8c Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Tue, 18 Nov 2025 13:11:23 +0100 Subject: [PATCH 029/571] fix: preserve vendor suffixes in KubeVersion.GitVersion Helm 3.19.0 introduced a regression where vendor-specific suffixes (e.g., -gke.1245000, -eks-4096722, +) are stripped from .Capabilities.KubeVersion.GitVersion, breaking charts that detect managed Kubernetes platforms. The root cause was using k8sversion.ParseGeneric().String() which intentionally discards vendor suffixes. The fix stores both the full version (with vendor suffix) and a normalized version. String() returns the normalized version for constraint checking (e.g., ">= 1.21.0"), while Version/GitVersion preserve the full string for template access. Fixes #31423 Related to #31063, #31078 Signed-off-by: Benoit Tigeot --- pkg/action/action.go | 2 +- pkg/action/install_test.go | 2 +- pkg/chart/common/capabilities.go | 45 ++++++++++++++++++------- pkg/chart/common/capabilities_test.go | 47 ++++++++++++++++++++------- 4 files changed, 70 insertions(+), 26 deletions(-) diff --git a/pkg/action/action.go b/pkg/action/action.go index 9555006be..273c55012 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -230,7 +230,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values, if ch.Metadata.KubeVersion != "" { if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) { - return hs, b, "", fmt.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String()) + return hs, b, "", fmt.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.Version) } } diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 9f04f40d4..374b318e1 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -572,7 +572,7 @@ func TestInstallRelease_KubeVersion(t *testing.T) { vals = map[string]interface{}{} _, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals) is.Error(err) - is.Contains(err.Error(), "chart requires kubeVersion") + is.Contains(err.Error(), "chart requires kubeVersion: >=99.0.0 which is incompatible with Kubernetes v1.20.") } func TestInstallRelease_Wait(t *testing.T) { diff --git a/pkg/chart/common/capabilities.go b/pkg/chart/common/capabilities.go index 355c3978a..9953abaf5 100644 --- a/pkg/chart/common/capabilities.go +++ b/pkg/chart/common/capabilities.go @@ -19,6 +19,7 @@ import ( "fmt" "slices" "strconv" + "strings" "k8s.io/client-go/kubernetes/scheme" @@ -39,11 +40,13 @@ var ( DefaultVersionSet = allKnownVersions() // DefaultCapabilities is the default set of capabilities. + version = fmt.Sprintf("v%s.%s.0", k8sVersionMajor, k8sVersionMinor) DefaultCapabilities = &Capabilities{ KubeVersion: KubeVersion{ - Version: fmt.Sprintf("v%s.%s.0", k8sVersionMajor, k8sVersionMinor), - Major: k8sVersionMajor, - Minor: k8sVersionMinor, + Version: version, + normalizedVersion: version, + Major: k8sVersionMajor, + Minor: k8sVersionMinor, }, APIVersions: DefaultVersionSet, HelmVersion: helmversion.Get(), @@ -70,15 +73,22 @@ func (capabilities *Capabilities) Copy() *Capabilities { // KubeVersion is the Kubernetes version. type KubeVersion struct { - Version string // Kubernetes version - Major string // Kubernetes major version - Minor string // Kubernetes minor version + Version string // Full version (e.g., v1.33.4-gke.1245000) + normalizedVersion string // Normalized for constraint checking (e.g., v1.33.4) + Major string // Kubernetes major version + Minor string // Kubernetes minor version } -// String implements fmt.Stringer -func (kv *KubeVersion) String() string { return kv.Version } +// String implements fmt.Stringer. +// Returns the normalized version used for constraint checking. +func (kv *KubeVersion) String() string { + if kv.normalizedVersion != "" { + return kv.normalizedVersion + } + return kv.Version +} -// GitVersion returns the Kubernetes version string. +// GitVersion returns the full Kubernetes version string. // // Deprecated: use KubeVersion.Version. func (kv *KubeVersion) GitVersion() string { return kv.Version } @@ -91,10 +101,21 @@ func ParseKubeVersion(version string) (*KubeVersion, error) { if err != nil { return nil, err } + + // Preserve original input (e.g., v1.33.4-gke.1245000) + gitVersion := version + if !strings.HasPrefix(version, "v") { + gitVersion = "v" + version + } + + // Normalize for constraint checking (strips all suffixes) + normalizedVer := "v" + sv.String() + return &KubeVersion{ - Version: "v" + sv.String(), - Major: strconv.FormatUint(uint64(sv.Major()), 10), - Minor: strconv.FormatUint(uint64(sv.Minor()), 10), + Version: gitVersion, + normalizedVersion: normalizedVer, + Major: strconv.FormatUint(uint64(sv.Major()), 10), + Minor: strconv.FormatUint(uint64(sv.Minor()), 10), }, nil } diff --git a/pkg/chart/common/capabilities_test.go b/pkg/chart/common/capabilities_test.go index bf32b1f3f..de6393077 100644 --- a/pkg/chart/common/capabilities_test.go +++ b/pkg/chart/common/capabilities_test.go @@ -83,18 +83,41 @@ func TestParseKubeVersion(t *testing.T) { } } -func TestParseKubeVersionSuffix(t *testing.T) { - kv, err := ParseKubeVersion("v1.28+") - if err != nil { - t.Errorf("Expected v1.28+ to parse successfully") - } - if kv.Version != "v1.28" { - t.Errorf("Expected parsed KubeVersion.Version to be v1.28, got %q", kv.String()) - } - if kv.Major != "1" { - t.Errorf("Expected parsed KubeVersion.Major to be 1, got %q", kv.Major) +func TestParseKubeVersionWithVendorSuffixes(t *testing.T) { + tests := []struct { + name string + input string + wantVer string + wantString string + wantMajor string + wantMinor string + }{ + {"GKE vendor suffix", "v1.33.4-gke.1245000", "v1.33.4-gke.1245000", "v1.33.4", "1", "33"}, + {"GKE without v", "1.30.2-gke.1587003", "v1.30.2-gke.1587003", "v1.30.2", "1", "30"}, + {"EKS trailing +", "v1.28+", "v1.28+", "v1.28", "1", "28"}, + {"EKS + without v", "1.28+", "v1.28+", "v1.28", "1", "28"}, + {"Standard version", "v1.31.0", "v1.31.0", "v1.31.0", "1", "31"}, + {"Standard without v", "1.29.0", "v1.29.0", "v1.29.0", "1", "29"}, } - if kv.Minor != "28" { - t.Errorf("Expected parsed KubeVersion.Minor to be 28, got %q", kv.Minor) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kv, err := ParseKubeVersion(tt.input) + if err != nil { + t.Fatalf("ParseKubeVersion() error = %v", err) + } + if kv.Version != tt.wantVer { + t.Errorf("Version = %q, want %q", kv.Version, tt.wantVer) + } + if kv.String() != tt.wantString { + t.Errorf("String() = %q, want %q", kv.String(), tt.wantString) + } + if kv.Major != tt.wantMajor { + t.Errorf("Major = %q, want %q", kv.Major, tt.wantMajor) + } + if kv.Minor != tt.wantMinor { + t.Errorf("Minor = %q, want %q", kv.Minor, tt.wantMinor) + } + }) } } From 2dc5864f447d0429f7101526a518529e9919b067 Mon Sep 17 00:00:00 2001 From: shuv0id <110290476+shuv0id@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:25:34 +0530 Subject: [PATCH 030/571] fix: add missing context to debug logs Adds chart name to dependency logs, namespace to resource waiting logs, and confirmation message when all resources are ready. Addresses #31520 Signed-off-by: shuv0id <110290476+shuv0id@users.noreply.github.com> --- pkg/chart/common/util/jsonschema.go | 2 +- pkg/kube/statuswait.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/chart/common/util/jsonschema.go b/pkg/chart/common/util/jsonschema.go index 649bbecac..6d7f32604 100644 --- a/pkg/chart/common/util/jsonschema.go +++ b/pkg/chart/common/util/jsonschema.go @@ -87,7 +87,7 @@ func ValidateAgainstSchema(ch chart.Charter, values map[string]interface{}) erro sb.WriteString(err.Error()) } } - slog.Debug("number of dependencies in the chart", "dependencies", len(chrt.Dependencies())) + slog.Debug("number of dependencies in the chart", "chart", chrt.Name(), "dependencies", len(chrt.Dependencies())) // For each dependency, recursively call this function with the coalesced values for _, subchart := range chrt.Dependencies() { sub, err := chart.NewAccessor(subchart) diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index 225321f6e..6077af165 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -231,6 +231,7 @@ func statusObserver(cancel context.CancelFunc, desired status.Status) collector. } if aggregator.AggregateStatus(rss, desired) == desired { + slog.Debug("all resources achieved desired status", "desiredStatus", desired, "resourceCount", len(rss)) cancel() return } @@ -241,7 +242,7 @@ func statusObserver(cancel context.CancelFunc, desired status.Status) collector. return nonDesiredResources[i].Identifier.Name < nonDesiredResources[j].Identifier.Name }) first := nonDesiredResources[0] - slog.Debug("waiting for resource", "name", first.Identifier.Name, "kind", first.Identifier.GroupKind.Kind, "expectedStatus", desired, "actualStatus", first.Status) + slog.Debug("waiting for resource", "namespace", first.Identifier.Namespace, "name", first.Identifier.Name, "kind", first.Identifier.GroupKind.Kind, "expectedStatus", desired, "actualStatus", first.Status) } } } From 63e060fb053f951c6042efad3fee975ae624a906 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Wed, 19 Nov 2025 12:49:49 -0500 Subject: [PATCH 031/571] bump version to 4.1 Signed-off-by: Matt Farina --- internal/version/version.go | 2 +- pkg/chart/common/capabilities_test.go | 4 ++-- pkg/cmd/testdata/output/version-short.txt | 2 +- pkg/cmd/testdata/output/version-template.txt | 2 +- pkg/cmd/testdata/output/version.txt | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/version/version.go b/internal/version/version.go index b7f2436a1..f9e76eab7 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -30,7 +30,7 @@ var ( // // Increment major number for new feature additions and behavioral changes. // Increment minor number for bug fixes and performance enhancements. - version = "v4.0" + version = "v4.1" // metadata is extra build time data metadata = "" diff --git a/pkg/chart/common/capabilities_test.go b/pkg/chart/common/capabilities_test.go index bf32b1f3f..716b903aa 100644 --- a/pkg/chart/common/capabilities_test.go +++ b/pkg/chart/common/capabilities_test.go @@ -62,8 +62,8 @@ func TestDefaultCapabilities(t *testing.T) { func TestDefaultCapabilitiesHelmVersion(t *testing.T) { hv := DefaultCapabilities.HelmVersion - if hv.Version != "v4.0" { - t.Errorf("Expected default HelmVersion to be v4.0, got %q", hv.Version) + if hv.Version != "v4.1" { + t.Errorf("Expected default HelmVersion to be v4.1, got %q", hv.Version) } } diff --git a/pkg/cmd/testdata/output/version-short.txt b/pkg/cmd/testdata/output/version-short.txt index 1961bcc21..8cf4318fb 100644 --- a/pkg/cmd/testdata/output/version-short.txt +++ b/pkg/cmd/testdata/output/version-short.txt @@ -1 +1 @@ -v4.0 +v4.1 diff --git a/pkg/cmd/testdata/output/version-template.txt b/pkg/cmd/testdata/output/version-template.txt index 1c3c8f5d7..8fd8b4962 100644 --- a/pkg/cmd/testdata/output/version-template.txt +++ b/pkg/cmd/testdata/output/version-template.txt @@ -1 +1 @@ -Version: v4.0 \ No newline at end of file +Version: v4.1 \ No newline at end of file diff --git a/pkg/cmd/testdata/output/version.txt b/pkg/cmd/testdata/output/version.txt index 2d50053f2..44c5bf470 100644 --- a/pkg/cmd/testdata/output/version.txt +++ b/pkg/cmd/testdata/output/version.txt @@ -1 +1 @@ -version.BuildInfo{Version:"v4.0", GitCommit:"", GitTreeState:"", GoVersion:"", KubeClientVersion:"v."} +version.BuildInfo{Version:"v4.1", GitCommit:"", GitTreeState:"", GoVersion:"", KubeClientVersion:"v."} From 28baa972c1c064a13be50956e52d3a79ae5ce54b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 21:16:24 +0000 Subject: [PATCH 032/571] chore(deps): bump github.com/cyphar/filepath-securejoin Bumps [github.com/cyphar/filepath-securejoin](https://github.com/cyphar/filepath-securejoin) from 0.6.0 to 0.6.1. - [Release notes](https://github.com/cyphar/filepath-securejoin/releases) - [Changelog](https://github.com/cyphar/filepath-securejoin/blob/main/CHANGELOG.md) - [Commits](https://github.com/cyphar/filepath-securejoin/compare/v0.6.0...v0.6.1) --- updated-dependencies: - dependency-name: github.com/cyphar/filepath-securejoin dependency-version: 0.6.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 495b2befa..98143cae1 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/Masterminds/vcs v1.13.3 github.com/ProtonMail/go-crypto v1.3.0 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 - github.com/cyphar/filepath-securejoin v0.6.0 + github.com/cyphar/filepath-securejoin v0.6.1 github.com/distribution/distribution/v3 v3.0.0 github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 diff --git a/go.sum b/go.sum index ba032ef14..c46561907 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= -github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= From c2405ce8c2bbbb90cc23f9ffb744442fc115fe11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 03:00:02 +0000 Subject: [PATCH 033/571] chore(deps): bump golang.org/x/crypto from 0.44.0 to 0.45.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.44.0 to 0.45.0. - [Commits](https://github.com/golang/crypto/compare/v0.44.0...v0.45.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.45.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 495b2befa..bd9f737f3 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/tetratelabs/wazero v1.10.1 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.44.0 + golang.org/x/crypto v0.45.0 golang.org/x/term v0.37.0 golang.org/x/text v0.31.0 gopkg.in/yaml.v3 v3.0.1 // indirect @@ -163,7 +163,7 @@ require ( go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/mod v0.29.0 // indirect - golang.org/x/net v0.46.0 // indirect + golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect diff --git a/go.sum b/go.sum index ba032ef14..440290a9c 100644 --- a/go.sum +++ b/go.sum @@ -396,8 +396,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -419,8 +419,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 40e22dedb2d9fdb8c47376a2c3071b99ba056d9c Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Thu, 20 Nov 2025 20:46:46 +0100 Subject: [PATCH 034/571] fix: prevent reporting fallback on version when none specified MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: https://github.com/helm/helm/issues/31548 ``` ❯ helm pull rancher/longhorn-crd --version 106.2.0+up1.8.2 --destination /tmp/ level=WARN msg="unable to find exact version; falling back to closest available version" chart=longhorn-crd requested=106.2.0+up1.8.2 selected=106.2.0+up1.8.1 ❯ bin/helm show chart brigade/brigade apiVersion: v1 appVersion: v1.5.0 dependencies: - condition: kashti.enabled name: kashti repository: https://brigadecore.github.io/charts version: 0.7.0 - condition: brigade-github-app.enabled name: brigade-github-app repository: https://brigadecore.github.io/charts version: 0.8.0 - alias: gw condition: gw.enabled name: brigade-github-oauth repository: https://brigadecore.github.io/charts version: 0.4.0 description: Brigade provides event-driven scripting of Kubernetes pipelines. name: brigade version: 1.10.0 ``` Signed-off-by: Benoit Tigeot --- pkg/repo/v1/index.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/repo/v1/index.go b/pkg/repo/v1/index.go index d77d70a7f..7969d64e9 100644 --- a/pkg/repo/v1/index.go +++ b/pkg/repo/v1/index.go @@ -215,7 +215,9 @@ func (i IndexFile) Get(name, version string) (*ChartVersion, error) { } if constraint.Check(test) { - slog.Warn("unable to find exact version; falling back to closest available version", "chart", name, "requested", version, "selected", ver.Version) + if len(version) != 0 { + slog.Warn("unable to find exact version requested; falling back to closest available version", "chart", name, "requested", version, "selected", ver.Version) + } return ver, nil } } From 81d244ca21e232e5ebccd67040cdf7596b572e8b Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Thu, 20 Nov 2025 13:28:12 +0100 Subject: [PATCH 035/571] fix: prevent segmentation violation on empty yaml in multidoc Fixes: https://github.com/helm/helm/issues/31544 Signed-off-by: Benoit Tigeot --- internal/chart/v3/lint/rules/crds.go | 6 +++-- internal/chart/v3/lint/rules/crds_test.go | 30 +++++++++++++++++++++++ pkg/chart/v2/lint/rules/crds.go | 6 +++-- pkg/chart/v2/lint/rules/crds_test.go | 30 +++++++++++++++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/internal/chart/v3/lint/rules/crds.go b/internal/chart/v3/lint/rules/crds.go index 6bafb52eb..735573624 100644 --- a/internal/chart/v3/lint/rules/crds.go +++ b/internal/chart/v3/lint/rules/crds.go @@ -80,8 +80,10 @@ func Crds(linter *support.Linter) { return } - linter.RunLinterRule(support.ErrorSev, fpath, validateCrdAPIVersion(yamlStruct)) - linter.RunLinterRule(support.ErrorSev, fpath, validateCrdKind(yamlStruct)) + if yamlStruct != nil { + linter.RunLinterRule(support.ErrorSev, fpath, validateCrdAPIVersion(yamlStruct)) + linter.RunLinterRule(support.ErrorSev, fpath, validateCrdKind(yamlStruct)) + } } } } diff --git a/internal/chart/v3/lint/rules/crds_test.go b/internal/chart/v3/lint/rules/crds_test.go index d93e3d978..e435b8ea3 100644 --- a/internal/chart/v3/lint/rules/crds_test.go +++ b/internal/chart/v3/lint/rules/crds_test.go @@ -17,6 +17,8 @@ limitations under the License. package rules import ( + "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -34,3 +36,31 @@ func TestInvalidCrdsDir(t *testing.T) { assert.Len(t, res, 1) assert.ErrorContains(t, res[0].Err, "not a directory") } + +// multi-document YAML with empty documents would panic +func TestCrdWithEmptyDocument(t *testing.T) { + chartDir := t.TempDir() + + os.WriteFile(filepath.Join(chartDir, "Chart.yaml"), []byte( + `apiVersion: v1 +name: test +version: 0.1.0 +`), 0644) + + // CRD with comments before --- (creates empty document) + crdsDir := filepath.Join(chartDir, "crds") + os.Mkdir(crdsDir, 0755) + os.WriteFile(filepath.Join(crdsDir, "test.yaml"), []byte( + `# Comments create empty document +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: test.example.io +`), 0644) + + linter := support.Linter{ChartDir: chartDir} + Crds(&linter) + + assert.Len(t, linter.Messages, 0) +} diff --git a/pkg/chart/v2/lint/rules/crds.go b/pkg/chart/v2/lint/rules/crds.go index 49e30192a..faef7dcf9 100644 --- a/pkg/chart/v2/lint/rules/crds.go +++ b/pkg/chart/v2/lint/rules/crds.go @@ -80,8 +80,10 @@ func Crds(linter *support.Linter) { return } - linter.RunLinterRule(support.ErrorSev, fpath, validateCrdAPIVersion(yamlStruct)) - linter.RunLinterRule(support.ErrorSev, fpath, validateCrdKind(yamlStruct)) + if yamlStruct != nil { + linter.RunLinterRule(support.ErrorSev, fpath, validateCrdAPIVersion(yamlStruct)) + linter.RunLinterRule(support.ErrorSev, fpath, validateCrdKind(yamlStruct)) + } } } } diff --git a/pkg/chart/v2/lint/rules/crds_test.go b/pkg/chart/v2/lint/rules/crds_test.go index e644f182f..228f40a66 100644 --- a/pkg/chart/v2/lint/rules/crds_test.go +++ b/pkg/chart/v2/lint/rules/crds_test.go @@ -17,6 +17,8 @@ limitations under the License. package rules import ( + "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -34,3 +36,31 @@ func TestInvalidCrdsDir(t *testing.T) { assert.Len(t, res, 1) assert.ErrorContains(t, res[0].Err, "not a directory") } + +// multi-document YAML with empty documents would panic +func TestCrdWithEmptyDocument(t *testing.T) { + chartDir := t.TempDir() + + os.WriteFile(filepath.Join(chartDir, "Chart.yaml"), []byte( + `apiVersion: v1 +name: test +version: 0.1.0 +`), 0644) + + // CRD with comments before --- (creates empty document) + crdsDir := filepath.Join(chartDir, "crds") + os.Mkdir(crdsDir, 0755) + os.WriteFile(filepath.Join(crdsDir, "test.yaml"), []byte( + `# Comments create empty document +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: test.example.io +`), 0644) + + linter := support.Linter{ChartDir: chartDir} + Crds(&linter) + + assert.Len(t, linter.Messages, 0) +} From b87f2da2f8b54a8bbc489ee7e8d853f80cab08f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 21:07:06 +0000 Subject: [PATCH 036/571] chore(deps): bump actions/setup-go from 5.5.0 to 6.1.0 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5.5.0 to 6.1.0. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/d35c59abb061a4a6fb18e82ac0862c26744d6ab5...4dc6199c7b1a012772edbd06daecab0f50c9053c) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/release.yml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index b43b41d39..f6b026ea6 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -22,7 +22,7 @@ jobs: - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0 + uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # pin@6.1.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index a14e4125b..f47a80028 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -17,7 +17,7 @@ jobs: - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0 + uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # pin@6.1.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index db57ed4b3..14816869b 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -18,7 +18,7 @@ jobs: - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0 + uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # pin@6.1.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9bbe0eeef..713e6cf5a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0 + uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # pin@6.1.0 with: go-version: '${{ env.GOLANG_VERSION }}' - name: Run unit tests @@ -90,7 +90,7 @@ jobs: run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@5.5.0 + uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # pin@6.1.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true From 4fddc646787231b68940bbcf88346bdc82534727 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 21:07:14 +0000 Subject: [PATCH 037/571] chore(deps): bump actions/checkout from 5.0.1 to 6.0.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.1 to 6.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/93cb6efe18208431cddfb8368fd83d5badbf9bfd...1af3b93b6815bc44a9784bd300feb67ff0d1eeb3) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index b43b41d39..af9601c31 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # pin@v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # pin@v6.0.0 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c7ff9e20a..5c52b86c4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -43,7 +43,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # pin@v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # pin@v6.0.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index a14e4125b..f3e53d24f 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # pin@v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # pin@v6.0.0 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index db57ed4b3..8abb45a03 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # pin@v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # pin@v6.0.0 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9bbe0eeef..425bf5345 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest-16-cores steps: - name: Checkout source code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # pin@v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # pin@v6.0.0 with: fetch-depth: 0 @@ -84,7 +84,7 @@ jobs: if: github.ref == 'refs/heads/main' && github.repository == 'helm/helm' steps: - name: Checkout source code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # pin@v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # pin@v6.0.0 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 0dbd24766..aad2594f4 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -28,7 +28,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false From cb35947ae600146ea0912fe9c2d0be27ee4181e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 21:07:43 +0000 Subject: [PATCH 038/571] chore(deps): bump golangci/golangci-lint-action from 9.0.0 to 9.1.0 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 9.0.0 to 9.1.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/0a35821d5c230e903fcfe077583637dea1b27b47...e7fa5ac41e1cf5b7d48e45e42232ce7ada589601) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-version: 9.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/golangci-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 186e53b60..b9d376519 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -22,6 +22,6 @@ jobs: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true - name: golangci-lint - uses: golangci/golangci-lint-action@0a35821d5c230e903fcfe077583637dea1b27b47 #pin@9.0.0 + uses: golangci/golangci-lint-action@e7fa5ac41e1cf5b7d48e45e42232ce7ada589601 #pin@9.1.0 with: version: ${{ env.GOLANGCI_LINT_VERSION }} From 936cd328ac59001f7a6716a3eb7e9075f3950f44 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Fri, 21 Nov 2025 17:45:22 -0500 Subject: [PATCH 039/571] Fix kube client logging The kube client logging is based on the actionConfig logging. This is setup to use slog.Default() before the logging flags are parsed and logging is setup. newRootCmdWithConfig changes the logging but it wasn't picked up for actionConfig or the kube client. This change updates the logging to include any changes. Signed-off-by: Matt Farina --- pkg/cmd/root.go | 10 ++++++++++ pkg/cmd/root_test.go | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index b64118a50..04ba91c1f 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -179,6 +179,16 @@ func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, arg logSetup(settings.Debug) + // newRootCmdWithConfig is only called from NewRootCmd. NewRootCmd sets up + // NewConfiguration without a custom logger. So, the slog default is used. logSetup + // can change the default logger to the one in the logger package. This happens for + // the Helm client. This means the actionConfig logger is different from the slog + // default logger. If they are different we sync the actionConfig logger to the slog + // current default one. + if actionConfig.Logger() != slog.Default() { + actionConfig.SetLogger(slog.Default().Handler()) + } + // Validate color mode setting switch settings.ColorMode { case "never", "auto", "always": diff --git a/pkg/cmd/root_test.go b/pkg/cmd/root_test.go index 84e3d9ed2..316e6bd2e 100644 --- a/pkg/cmd/root_test.go +++ b/pkg/cmd/root_test.go @@ -17,11 +17,14 @@ limitations under the License. package cmd import ( + "bytes" + "log/slog" "os" "path/filepath" "testing" "helm.sh/helm/v4/internal/test/ensure" + "helm.sh/helm/v4/pkg/action" "helm.sh/helm/v4/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath/xdg" ) @@ -129,3 +132,20 @@ func TestUnknownSubCmd(t *testing.T) { // func TestRootFileCompletion(t *testing.T) { // checkFileCompletion(t, "", false) // } + +func TestRootCmdLogger(t *testing.T) { + args := []string{} + buf := new(bytes.Buffer) + actionConfig := action.NewConfiguration() + _, err := newRootCmdWithConfig(actionConfig, buf, args, SetupLogging) + if err != nil { + t.Errorf("expected no error, got: '%v'", err) + } + + l1 := actionConfig.Logger() + l2 := slog.Default() + + if l1.Handler() != l2.Handler() { + t.Error("expected actionConfig logger to be the slog default logger") + } +} From 380abe2b1643e1a8eec9cd575d1d5c0521cf1c31 Mon Sep 17 00:00:00 2001 From: Mads Jensen Date: Sat, 22 Nov 2025 10:05:18 +0100 Subject: [PATCH 040/571] Remove two redundant if-checks. These were found with the nilness checker. Signed-off-by: Mads Jensen --- pkg/downloader/chart_downloader.go | 5 +---- pkg/repo/v1/index_test.go | 4 +--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 00c8c56e8..190aaebf9 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -227,13 +227,10 @@ func (c *ChartDownloader) DownloadToCache(ref, version string) (string, *provena // Check the cache for the file digest, err := hex.DecodeString(digestString) if err != nil { - return "", nil, err + return "", nil, fmt.Errorf("unable to decode digest: %w", err) } var digest32 [32]byte copy(digest32[:], digest) - if err != nil { - return "", nil, fmt.Errorf("unable to decode digest: %w", err) - } var pth string // only fetch from the cache if we have a digest diff --git a/pkg/repo/v1/index_test.go b/pkg/repo/v1/index_test.go index a8aadadec..517457dc4 100644 --- a/pkg/repo/v1/index_test.go +++ b/pkg/repo/v1/index_test.go @@ -708,9 +708,7 @@ func TestLoadIndex_DuplicateChartDeps(t *testing.T) { } cvs := idx.Entries["nginx"] if cvs == nil { - if err != nil { - t.Error("expected one chart version not to be filtered out") - } + t.Error("expected one chart version not to be filtered out") } for _, v := range cvs { if v.Name == "alpine" { From 6cceead3994b9e8490f6f9ad40b49456169d015d Mon Sep 17 00:00:00 2001 From: Mads Jensen Date: Sat, 22 Nov 2025 18:51:52 +0100 Subject: [PATCH 041/571] Use modernize to use newer Golang features. The rangeint suggestion is faster. The maps.Copy and slices.Contains are just syntatic sugar. Signed-off-by: Mads Jensen --- pkg/action/package_test.go | 2 +- pkg/chart/loader/load_test.go | 5 ++--- pkg/cmd/package.go | 2 +- pkg/cmd/plugin_test.go | 2 +- pkg/cmd/pull.go | 2 +- pkg/cmd/repo_add_test.go | 4 ++-- pkg/cmd/search/search_test.go | 4 ++-- pkg/cmd/uninstall.go | 2 +- pkg/engine/engine_test.go | 8 ++++---- pkg/registry/generic.go | 7 +++---- pkg/storage/driver/sql_test.go | 2 +- 11 files changed, 19 insertions(+), 21 deletions(-) diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go index 12bea10dd..b0ca0bec4 100644 --- a/pkg/action/package_test.go +++ b/pkg/action/package_test.go @@ -93,7 +93,7 @@ func TestPassphraseFileFetcher_WithStdinAndMultipleFetches(t *testing.T) { w.Write([]byte(passphrase + "\n")) }() - for i := 0; i < 4; i++ { + for range 4 { fetcher, err := testPkg.passphraseFileFetcher("-", stdin) if err != nil { t.Errorf("Expected passphraseFileFetcher to not return an error, but got %v", err) diff --git a/pkg/chart/loader/load_test.go b/pkg/chart/loader/load_test.go index 401772557..40f46c09b 100644 --- a/pkg/chart/loader/load_test.go +++ b/pkg/chart/loader/load_test.go @@ -22,6 +22,7 @@ import ( "compress/gzip" "fmt" "io" + "maps" "path/filepath" "strings" "testing" @@ -40,9 +41,7 @@ func createChartArchive(t *testing.T, chartName, apiVersion string, extraFiles m tw := tar.NewWriter(gw) files := make(map[string][]byte) - for k, v := range extraFiles { - files[k] = v - } + maps.Copy(files, extraFiles) if createChartYaml { chartYAMLContent := fmt.Sprintf(`apiVersion: %s diff --git a/pkg/cmd/package.go b/pkg/cmd/package.go index 36a7dff73..96c0c47b2 100644 --- a/pkg/cmd/package.go +++ b/pkg/cmd/package.go @@ -81,7 +81,7 @@ func newPackageCmd(out io.Writer) *cobra.Command { return fmt.Errorf("missing registry client: %w", err) } - for i := 0; i < len(args); i++ { + for i := range args { path, err := filepath.Abs(args[i]) if err != nil { return err diff --git a/pkg/cmd/plugin_test.go b/pkg/cmd/plugin_test.go index f7a418569..05cfe46f1 100644 --- a/pkg/cmd/plugin_test.go +++ b/pkg/cmd/plugin_test.go @@ -199,7 +199,7 @@ func TestLoadPluginsWithSpace(t *testing.T) { t.Fatalf("Expected %d plugins, got %d", len(tests), len(plugins)) } - for i := 0; i < len(plugins); i++ { + for i := range plugins { out.Reset() tt := tests[i] pp := plugins[i] diff --git a/pkg/cmd/pull.go b/pkg/cmd/pull.go index 922698c6c..bb7a8d1c0 100644 --- a/pkg/cmd/pull.go +++ b/pkg/cmd/pull.go @@ -72,7 +72,7 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } client.SetRegistryClient(registryClient) - for i := 0; i < len(args); i++ { + for i := range args { output, err := client.Run(args[i]) if err != nil { return err diff --git a/pkg/cmd/repo_add_test.go b/pkg/cmd/repo_add_test.go index 6d3696f52..df9451d34 100644 --- a/pkg/cmd/repo_add_test.go +++ b/pkg/cmd/repo_add_test.go @@ -200,7 +200,7 @@ func repoAddConcurrent(t *testing.T, testName, repoFile string) { var wg sync.WaitGroup wg.Add(3) - for i := 0; i < 3; i++ { + for i := range 3 { go func(name string) { defer wg.Done() o := &repoAddOptions{ @@ -227,7 +227,7 @@ func repoAddConcurrent(t *testing.T, testName, repoFile string) { } var name string - for i := 0; i < 3; i++ { + for i := range 3 { name = fmt.Sprintf("%s-%d", testName, i) if !f.Has(name) { t.Errorf("%s was not successfully inserted into %s: %s", name, repoFile, f.Repositories[0]) diff --git a/pkg/cmd/search/search_test.go b/pkg/cmd/search/search_test.go index a24eb1f64..b3220394f 100644 --- a/pkg/cmd/search/search_test.go +++ b/pkg/cmd/search/search_test.go @@ -39,13 +39,13 @@ func TestSortScore(t *testing.T) { SortScore(in) // Test Score - for i := 0; i < len(expectScore); i++ { + for i := range expectScore { if expectScore[i] != in[i].Score { t.Errorf("Sort error on index %d: expected %d, got %d", i, expectScore[i], in[i].Score) } } // Test Name - for i := 0; i < len(expect); i++ { + for i := range expect { if expect[i] != in[i].Name { t.Errorf("Sort error: expected %s, got %s", expect[i], in[i].Name) } diff --git a/pkg/cmd/uninstall.go b/pkg/cmd/uninstall.go index 4680c324a..4cc14ae1e 100644 --- a/pkg/cmd/uninstall.go +++ b/pkg/cmd/uninstall.go @@ -55,7 +55,7 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if validationErr != nil { return validationErr } - for i := 0; i < len(args); i++ { + for i := range args { res, err := client.Run(args[i]) if err != nil { diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index e541ef9d7..c9cdf79c3 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -169,7 +169,7 @@ func TestRenderRefsOrdering(t *testing.T) { "parent/templates/test.yaml": "parent value", } - for i := 0; i < 100; i++ { + for i := range 100 { out, err := Render(parentChart, common.Values{}) if err != nil { t.Fatalf("Failed to render templates: %s", err) @@ -431,7 +431,7 @@ func TestParallelRenderInternals(t *testing.T) { // Make sure that we can use one Engine to run parallel template renders. e := new(Engine) var wg sync.WaitGroup - for i := 0; i < 20; i++ { + for i := range 20 { wg.Add(1) go func(i int) { tt := fmt.Sprintf("expect-%d", i) @@ -1041,7 +1041,7 @@ func TestRenderRecursionLimit(t *testing.T) { phrase := "All work and no play makes Jack a dull boy" printFunc := `{{define "overlook"}}{{printf "` + phrase + `\n"}}{{end}}` var repeatedIncl strings.Builder - for i := 0; i < times; i++ { + for range times { repeatedIncl.WriteString(`{{include "overlook" . }}`) } @@ -1059,7 +1059,7 @@ func TestRenderRecursionLimit(t *testing.T) { } var expect string - for i := 0; i < times; i++ { + for range times { expect += phrase + "\n" } if got := out["overlook/templates/quote"]; got != expect { diff --git a/pkg/registry/generic.go b/pkg/registry/generic.go index fb7e80d10..b46133d91 100644 --- a/pkg/registry/generic.go +++ b/pkg/registry/generic.go @@ -20,6 +20,7 @@ import ( "context" "io" "net/http" + "slices" "sort" "sync" @@ -124,10 +125,8 @@ func (c *GenericClient) PullGeneric(ref string, options GenericPullOptions) (*Ge mediaType := desc.MediaType // Skip media types if specified - for _, skipType := range options.SkipMediaTypes { - if mediaType == skipType { - return oras.SkipNode - } + if slices.Contains(options.SkipMediaTypes, mediaType) { + return oras.SkipNode } // Filter by allowed media types if specified diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go index d85691a6f..f7c29033c 100644 --- a/pkg/storage/driver/sql_test.go +++ b/pkg/storage/driver/sql_test.go @@ -120,7 +120,7 @@ func TestSQLList(t *testing.T) { sqlDriver, mock := newTestFixtureSQL(t) - for i := 0; i < 3; i++ { + for range 3 { query := fmt.Sprintf( "SELECT %s, %s, %s FROM %s WHERE %s = $1 AND %s = $2", sqlReleaseTableKeyColumn, From 2820ebe8c97b7d7b8a447375b74c9cb3741a4ffa Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Sun, 23 Nov 2025 21:58:23 +0530 Subject: [PATCH 042/571] fixed: --dry-run=server now respect generateName Signed-off-by: Mujib Ahasan --- pkg/kube/resource.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/kube/resource.go b/pkg/kube/resource.go index d88b171f0..98d2cb7f9 100644 --- a/pkg/kube/resource.go +++ b/pkg/kube/resource.go @@ -29,6 +29,9 @@ func (r *ResourceList) Append(val *resource.Info) { // Visit implements resource.Visitor. The visitor stops if fn returns an error. func (r ResourceList) Visit(fn resource.VisitorFunc) error { for _, i := range r { + if i.Name == "" { + continue + } if err := fn(i, nil); err != nil { return err } From b859163fd2b71d84111b3caaab1c72ab4852d26d Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Sun, 23 Nov 2025 10:49:13 -0800 Subject: [PATCH 043/571] Bump required go version (`go.mod` version) Explicit PR to bump the version, as needed by: https://github.com/helm/helm/pull/31215 Signed-off-by: George Jenkins --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 0561bcc44..cf15a9535 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module helm.sh/helm/v4 -go 1.24.0 +go 1.25.0 require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 From b357bcae8640508f110b7e63a8dfacd865c27b6e Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Mon, 24 Nov 2025 01:16:13 +0530 Subject: [PATCH 044/571] update: business logic respected for skipping object missing name Signed-off-by: Mujib Ahasan --- pkg/action/validate.go | 8 ++++++++ pkg/kube/resource.go | 3 --- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/action/validate.go b/pkg/action/validate.go index 761ccba47..d89ae5038 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -46,6 +46,10 @@ func requireAdoption(resources kube.ResourceList) (kube.ResourceList, error) { return err } + if info.Name == "" { + return nil + } + helper := resource.NewHelper(info.Client, info.Mapping) _, err = helper.Get(info.Namespace, info.Name) if err != nil { @@ -70,6 +74,10 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN return err } + if info.Name == "" { + return nil + } + helper := resource.NewHelper(info.Client, info.Mapping) existing, err := helper.Get(info.Namespace, info.Name) if err != nil { diff --git a/pkg/kube/resource.go b/pkg/kube/resource.go index 98d2cb7f9..d88b171f0 100644 --- a/pkg/kube/resource.go +++ b/pkg/kube/resource.go @@ -29,9 +29,6 @@ func (r *ResourceList) Append(val *resource.Info) { // Visit implements resource.Visitor. The visitor stops if fn returns an error. func (r ResourceList) Visit(fn resource.VisitorFunc) error { for _, i := range r { - if i.Name == "" { - continue - } if err := fn(i, nil); err != nil { return err } From 64bae717c58e80f05a60b84ddcd1f78387b4caee Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Mon, 24 Nov 2025 03:40:49 -0700 Subject: [PATCH 045/571] fxi: test concurrency download index Signed-off-by: Terry Howe --- pkg/downloader/chart_downloader.go | 2 +- pkg/getter/getter_test.go | 3 +- pkg/getter/httpgetter.go | 83 ++++++++++++++++-------------- pkg/getter/httpgetter_test.go | 10 ++-- 4 files changed, 51 insertions(+), 47 deletions(-) diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 00c8c56e8..10e570c32 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -176,7 +176,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven } } if !found { - body, err = g.Get(u.String() + ".prov") + body, err = g.Get(u.String()+".prov", c.Options...) if err != nil { if c.Verify == VerifyAlways { return destfile, ver, fmt.Errorf("failed to fetch provenance %q", u.String()+".prov") diff --git a/pkg/getter/getter_test.go b/pkg/getter/getter_test.go index 83920e809..3a09b4d82 100644 --- a/pkg/getter/getter_test.go +++ b/pkg/getter/getter_test.go @@ -60,7 +60,8 @@ func TestProvidersWithTimeout(t *testing.T) { if err != nil { t.Error(err) } - client, err := getter.(*HTTPGetter).httpClient() + httpGetter := getter.(*HTTPGetter) + client, err := httpGetter.httpClient(httpGetter.opts) if err != nil { t.Error(err) } diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go index 110f45c54..2bc12bdbf 100644 --- a/pkg/getter/httpgetter.go +++ b/pkg/getter/httpgetter.go @@ -37,13 +37,15 @@ type HTTPGetter struct { // Get performs a Get from repo.Getter and returns the body. func (g *HTTPGetter) Get(href string, options ...Option) (*bytes.Buffer, error) { + // Create a local copy of options to avoid data races when Get is called concurrently + opts := g.opts for _, opt := range options { - opt(&g.opts) + opt(&opts) } - return g.get(href) + return g.get(href, opts) } -func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) { +func (g *HTTPGetter) get(href string, opts getterOptions) (*bytes.Buffer, error) { // Set a helm specific user agent so that a repo server and metrics can // separate helm calls from other tools interacting with repos. req, err := http.NewRequest(http.MethodGet, href, nil) @@ -51,18 +53,18 @@ func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) { return nil, err } - if g.opts.acceptHeader != "" { - req.Header.Set("Accept", g.opts.acceptHeader) + if opts.acceptHeader != "" { + req.Header.Set("Accept", opts.acceptHeader) } req.Header.Set("User-Agent", version.GetUserAgent()) - if g.opts.userAgent != "" { - req.Header.Set("User-Agent", g.opts.userAgent) + if opts.userAgent != "" { + req.Header.Set("User-Agent", opts.userAgent) } // Before setting the basic auth credentials, make sure the URL associated // with the basic auth is the one being fetched. - u1, err := url.Parse(g.opts.url) + u1, err := url.Parse(opts.url) if err != nil { return nil, fmt.Errorf("unable to parse getter URL: %w", err) } @@ -74,13 +76,13 @@ func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) { // Host on URL (returned from url.Parse) contains the port if present. // This check ensures credentials are not passed between different // services on different ports. - if g.opts.passCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) { - if g.opts.username != "" && g.opts.password != "" { - req.SetBasicAuth(g.opts.username, g.opts.password) + if opts.passCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) { + if opts.username != "" && opts.password != "" { + req.SetBasicAuth(opts.username, opts.password) } } - client, err := g.httpClient() + client, err := g.httpClient(opts) if err != nil { return nil, err } @@ -110,51 +112,52 @@ func NewHTTPGetter(options ...Option) (Getter, error) { return &client, nil } -func (g *HTTPGetter) httpClient() (*http.Client, error) { - if g.opts.transport != nil { +func (g *HTTPGetter) httpClient(opts getterOptions) (*http.Client, error) { + if opts.transport != nil { return &http.Client{ - Transport: g.opts.transport, - Timeout: g.opts.timeout, + Transport: opts.transport, + Timeout: opts.timeout, }, nil } - g.once.Do(func() { - g.transport = &http.Transport{ + // Check if we need custom TLS configuration + needsCustomTLS := (opts.certFile != "" && opts.keyFile != "") || opts.caFile != "" || opts.insecureSkipVerifyTLS + + if needsCustomTLS { + // Create a new transport for custom TLS to avoid race conditions + transport := &http.Transport{ DisableCompression: true, Proxy: http.ProxyFromEnvironment, - // Being nil would cause the tls.Config default to be used - // "NewTLSConfig" modifies an empty TLS config, not the default one - TLSClientConfig: &tls.Config{}, } - }) - if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" || g.opts.insecureSkipVerifyTLS { tlsConf, err := tlsutil.NewTLSConfig( - tlsutil.WithInsecureSkipVerify(g.opts.insecureSkipVerifyTLS), - tlsutil.WithCertKeyPairFiles(g.opts.certFile, g.opts.keyFile), - tlsutil.WithCAFile(g.opts.caFile), + tlsutil.WithInsecureSkipVerify(opts.insecureSkipVerifyTLS), + tlsutil.WithCertKeyPairFiles(opts.certFile, opts.keyFile), + tlsutil.WithCAFile(opts.caFile), ) if err != nil { return nil, fmt.Errorf("can't create TLS config for client: %w", err) } - g.transport.TLSClientConfig = tlsConf + transport.TLSClientConfig = tlsConf + + return &http.Client{ + Transport: transport, + Timeout: opts.timeout, + }, nil } - if g.opts.insecureSkipVerifyTLS { - if g.transport.TLSClientConfig == nil { - g.transport.TLSClientConfig = &tls.Config{ - InsecureSkipVerify: true, - } - } else { - g.transport.TLSClientConfig.InsecureSkipVerify = true + // Use shared transport for default case (no custom TLS) + g.once.Do(func() { + g.transport = &http.Transport{ + DisableCompression: true, + Proxy: http.ProxyFromEnvironment, + TLSClientConfig: &tls.Config{}, } - } + }) - client := &http.Client{ + return &http.Client{ Transport: g.transport, - Timeout: g.opts.timeout, - } - - return client, nil + Timeout: opts.timeout, + }, nil } diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index b27b9f5d2..3047ed6f6 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -577,7 +577,7 @@ func TestHttpClientInsecureSkipVerify(t *testing.T) { func verifyInsecureSkipVerify(t *testing.T, g *HTTPGetter, caseName string, expectedValue bool) *http.Transport { t.Helper() - returnVal, err := g.httpClient() + returnVal, err := g.httpClient(g.opts) if err != nil { t.Fatal(err) @@ -601,7 +601,7 @@ func verifyInsecureSkipVerify(t *testing.T, g *HTTPGetter, caseName string, expe func TestDefaultHTTPTransportReuse(t *testing.T) { g := HTTPGetter{} - httpClient1, err := g.httpClient() + httpClient1, err := g.httpClient(g.opts) if err != nil { t.Fatal(err) @@ -613,7 +613,7 @@ func TestDefaultHTTPTransportReuse(t *testing.T) { transport1 := (httpClient1.Transport).(*http.Transport) //nolint:staticcheck - httpClient2, err := g.httpClient() + httpClient2, err := g.httpClient(g.opts) if err != nil { t.Fatal(err) @@ -635,7 +635,7 @@ func TestHTTPTransportOption(t *testing.T) { g := HTTPGetter{} g.opts.transport = transport - httpClient1, err := g.httpClient() + httpClient1, err := g.httpClient(g.opts) if err != nil { t.Fatal(err) @@ -651,7 +651,7 @@ func TestHTTPTransportOption(t *testing.T) { t.Fatalf("Expected transport option to be applied") } - httpClient2, err := g.httpClient() + httpClient2, err := g.httpClient(g.opts) if err != nil { t.Fatal(err) From e71a29ce4ff045c8194625c0d804f789d121023e Mon Sep 17 00:00:00 2001 From: megha1906 Date: Wed, 26 Nov 2025 18:00:37 +0530 Subject: [PATCH 046/571] chore: fix typo in pkg/downloader/chart_downloader.go Signed-off-by: megha1906 --- pkg/downloader/chart_downloader.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 190aaebf9..ee4f8abe3 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -80,7 +80,7 @@ type ChartDownloader struct { // ContentCache is the location where Cache stores its files by default // In previous versions of Helm the charts were put in the RepositoryCache. The - // repositories and charts are stored in 2 difference caches. + // repositories and charts are stored in 2 different caches. ContentCache string // Cache specifies the cache implementation to use. @@ -104,7 +104,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven return "", nil, errors.New("content cache must be set") } c.Cache = &DiskCache{Root: c.ContentCache} - slog.Debug("setup up default downloader cache") + slog.Debug("set up default downloader cache") } hash, u, err := c.ResolveChartVersion(ref, version) if err != nil { @@ -209,7 +209,7 @@ func (c *ChartDownloader) DownloadToCache(ref, version string) (string, *provena return "", nil, errors.New("content cache must be set") } c.Cache = &DiskCache{Root: c.ContentCache} - slog.Debug("setup up default downloader cache") + slog.Debug("set up default downloader cache") } digestString, u, err := c.ResolveChartVersion(ref, version) From 7a557580e1b5aac8327331e720e74bbbe49c0ad7 Mon Sep 17 00:00:00 2001 From: Michelle Fernandez Bieber Date: Sat, 22 Jan 2022 23:20:18 +0100 Subject: [PATCH 047/571] added shutdown hook that is executed after the logs have been retrieved Signed-off-by: Michelle Fernandez Bieber --- pkg/action/hooks.go | 75 ++++++++++++++++++++++------------- pkg/action/release_testing.go | 20 +++++----- pkg/cmd/release_testing.go | 3 +- 3 files changed, 61 insertions(+), 37 deletions(-) diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index 1e4fec9bd..c86964c65 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -33,6 +33,24 @@ import ( // execHook executes all of the hooks for the given hook event. func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) error { + shutdown, err := cfg.execHookWithDelayedShutdown(rl, hook, waitStrategy, timeout, serverSideApply) + if err != nil { + if err := shutdown(); err != nil { + return err + } + return err + } + return shutdown() +} + +type ExecuteShutdownHooks = func() error + +func ShutdownNoOp() error { + return nil +} + +// execHook executes all of the hooks for the given hook event and return a shutdownHook function to trigger deletions after doing other things like e.g. retrieving logs. +func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) (ExecuteShutdownHooks, error) { executingHooks := []*release.Hook{} for _, h := range rl.Hooks { @@ -51,12 +69,12 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, cfg.hookSetDeletePolicy(h) if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, waitStrategy, timeout); err != nil { - return err + return ShutdownNoOp, err } resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), true) if err != nil { - return fmt.Errorf("unable to build kubernetes object for %s hook %s: %w", hook, h.Path, err) + return ShutdownNoOp, fmt.Errorf("unable to build kubernetes object for %s hook %s: %w", hook, h.Path, err) } // Record the time at which the hook was applied to the cluster @@ -77,12 +95,12 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, kube.ClientCreateOptionServerSideApply(serverSideApply, false)); err != nil { h.LastRun.CompletedAt = time.Now() h.LastRun.Phase = release.HookPhaseFailed - return fmt.Errorf("warning: Hook %s %s failed: %w", hook, h.Path, err) + return ShutdownNoOp, fmt.Errorf("warning: Hook %s %s failed: %w", hook, h.Path, err) } waiter, err := cfg.KubeClient.GetWaiter(waitStrategy) if err != nil { - return fmt.Errorf("unable to get waiter: %w", err) + return ShutdownNoOp, fmt.Errorf("unable to get waiter: %w", err) } // Watch hook resources until they have completed err = waiter.WatchUntilReady(resources, timeout) @@ -98,36 +116,39 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, } // If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted // under failed condition. If so, then clear the corresponding resource object in the hook - if errDeleting := cfg.deleteHookByPolicy(h, release.HookFailed, waitStrategy, timeout); errDeleting != nil { - // We log the error here as we want to propagate the hook failure upwards to the release object. - log.Printf("error deleting the hook resource on hook failure: %v", errDeleting) - } - - // If a hook is failed, check the annotation of the previous successful hooks to determine whether the hooks - // should be deleted under succeeded condition. - if err := cfg.deleteHooksByPolicy(executingHooks[0:i], release.HookSucceeded, waitStrategy, timeout); err != nil { + return func() error { + if errDeleting := cfg.deleteHookByPolicy(h, release.HookFailed, waitStrategy, timeout); errDeleting != nil { + // We log the error here as we want to propagate the hook failure upwards to the release object. + log.Printf("error deleting the hook resource on hook failure: %v", errDeleting) + } + + // If a hook is failed, check the annotation of the previous successful hooks to determine whether the hooks + // should be deleted under succeeded condition. + if err := cfg.deleteHooksByPolicy(executingHooks[0:i], release.HookSucceeded, waitStrategy, timeout); err != nil { + return err + } return err - } + }, err - return err } h.LastRun.Phase = release.HookPhaseSucceeded } - // If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted - // or output should be logged under succeeded condition. If so, then clear the corresponding resource object in each hook - for i := len(executingHooks) - 1; i >= 0; i-- { - h := executingHooks[i] - if err := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnSucceeded); err != nil { - // We log here as we still want to attempt hook resource deletion even if output logging fails. - log.Printf("error outputting logs for hook failure: %v", err) - } - if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, waitStrategy, timeout); err != nil { - return err + return func() error { + // If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted + // or output should be logged under succeeded condition. If so, then clear the corresponding resource object in each hook + for i := len(executingHooks) - 1; i >= 0; i-- { + h := executingHooks[i] + if err := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnSucceeded); err != nil { + // We log here as we still want to attempt hook resource deletion even if output logging fails. + log.Printf("error outputting logs for hook failure: %v", err) + } + if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, waitStrategy, timeout); err != nil { + return err + } } - } - - return nil + return nil + }, nil } // hookByWeight is a sorter for hooks diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index b649579f4..2aa063515 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -57,24 +57,24 @@ func NewReleaseTesting(cfg *Configuration) *ReleaseTesting { } // Run executes 'helm test' against the given release. -func (r *ReleaseTesting) Run(name string) (ri.Releaser, error) { +func (r *ReleaseTesting) Run(name string) (ri.Releaser, ExecuteShutdownHooks, error) { if err := r.cfg.KubeClient.IsReachable(); err != nil { - return nil, err + return nil, ShutdownNoOp, err } if err := chartutil.ValidateReleaseName(name); err != nil { - return nil, fmt.Errorf("releaseTest: Release name is invalid: %s", name) + return nil, ShutdownNoOp, fmt.Errorf("releaseTest: Release name is invalid: %s", name) } // finds the non-deleted release with the given name reli, err := r.cfg.Releases.Last(name) if err != nil { - return reli, err + return reli, ShutdownNoOp, err } rel, err := releaserToV1Release(reli) if err != nil { - return rel, err + return reli, ShutdownNoOp, err } skippedHooks := []*release.Hook{} @@ -102,14 +102,16 @@ func (r *ReleaseTesting) Run(name string) (ri.Releaser, error) { } serverSideApply := rel.ApplyMethod == string(release.ApplyMethodServerSideApply) - if err := r.cfg.execHook(rel, release.HookTest, kube.StatusWatcherStrategy, r.Timeout, serverSideApply); err != nil { + shutdown, err := r.cfg.execHookWithDelayedShutdown(rel, release.HookTest, kube.StatusWatcherStrategy, r.Timeout, serverSideApply) + + if err != nil { rel.Hooks = append(skippedHooks, rel.Hooks...) - r.cfg.Releases.Update(rel) - return rel, err + r.cfg.Releases.Update(reli) + return reli, shutdown, err } rel.Hooks = append(skippedHooks, rel.Hooks...) - return rel, r.cfg.Releases.Update(rel) + return reli, shutdown, r.cfg.Releases.Update(reli) } // GetPodLogs will write the logs for all test pods in the given release into diff --git a/pkg/cmd/release_testing.go b/pkg/cmd/release_testing.go index 88a6f351f..3d6921e71 100644 --- a/pkg/cmd/release_testing.go +++ b/pkg/cmd/release_testing.go @@ -65,7 +65,8 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command client.Filters[action.ExcludeNameFilter] = append(client.Filters[action.ExcludeNameFilter], notName.ReplaceAllLiteralString(f, "")) } } - reli, runErr := client.Run(args[0]) + reli, shutdown, runErr := client.Run(args[0]) + defer shutdown() // We only return an error if we weren't even able to get the // release, otherwise we keep going so we can print status and logs // if requested From 10714772bedf15c207bccf85be21f2e244d4a4ff Mon Sep 17 00:00:00 2001 From: Michelle Fernandez Bieber Date: Sun, 23 Jan 2022 00:17:09 +0100 Subject: [PATCH 048/571] updated comment and made defer of shutdown function return errors as before and not the possible shutdown error Signed-off-by: Michelle Fernandez Bieber --- pkg/action/hooks.go | 2 +- pkg/cmd/release_testing.go | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index c86964c65..3a0aabb61 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -49,7 +49,7 @@ func ShutdownNoOp() error { return nil } -// execHook executes all of the hooks for the given hook event and return a shutdownHook function to trigger deletions after doing other things like e.g. retrieving logs. +// execHook executes all of the hooks for the given hook event and returns a shutdownHook function to trigger deletions after doing other things like e.g. retrieving logs. func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) (ExecuteShutdownHooks, error) { executingHooks := []*release.Hook{} diff --git a/pkg/cmd/release_testing.go b/pkg/cmd/release_testing.go index 3d6921e71..30de160ed 100644 --- a/pkg/cmd/release_testing.go +++ b/pkg/cmd/release_testing.go @@ -55,7 +55,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command } return compListReleases(toComplete, args, cfg) }, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) (returnError error) { client.Namespace = settings.Namespace() notName := regexp.MustCompile(`^!\s?name=`) for _, f := range filter { @@ -65,8 +65,17 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command client.Filters[action.ExcludeNameFilter] = append(client.Filters[action.ExcludeNameFilter], notName.ReplaceAllLiteralString(f, "")) } } + reli, shutdown, runErr := client.Run(args[0]) - defer shutdown() + defer func() { + if shutdownErr := shutdown(); shutdownErr != nil { + if returnError == nil { + returnError = shutdownErr + } + } + }() + + // We only return an error if we weren't even able to get the // release, otherwise we keep going so we can print status and logs // if requested From 7a61ebf01370d67684681c0d210ddec90fda3503 Mon Sep 17 00:00:00 2001 From: Michelle Fernandez Bieber Date: Sun, 23 Jan 2022 00:25:32 +0100 Subject: [PATCH 049/571] cleaned up empty line Signed-off-by: Michelle Fernandez Bieber --- pkg/action/hooks.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index 3a0aabb61..bd8e96e74 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -129,7 +129,6 @@ func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook } return err }, err - } h.LastRun.Phase = release.HookPhaseSucceeded } From d9301441f47bb8cf4c84ffcbed22f39ba70b1588 Mon Sep 17 00:00:00 2001 From: Michelle Fernandez Bieber Date: Fri, 22 Apr 2022 22:55:35 +0200 Subject: [PATCH 050/571] added check for nil shutdown Signed-off-by: Michelle Fernandez Bieber --- pkg/action/hooks.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index bd8e96e74..adf7478f6 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -34,6 +34,9 @@ import ( // execHook executes all of the hooks for the given hook event. func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) error { shutdown, err := cfg.execHookWithDelayedShutdown(rl, hook, waitStrategy, timeout, serverSideApply) + if shutdown == nil { + return nil + } if err != nil { if err := shutdown(); err != nil { return err From 6bb5bcc212fd014ad521eab8b1ffd94f3f4f0f71 Mon Sep 17 00:00:00 2001 From: Michelle Fernandez Bieber <37021266+mfbieber@users.noreply.github.com> Date: Sun, 23 Jul 2023 08:17:57 +0200 Subject: [PATCH 051/571] Update pkg/action/hooks.go Co-authored-by: Marco Lecheler Signed-off-by: Michelle Fernandez Bieber <37021266+mfbieber@users.noreply.github.com> --- pkg/action/hooks.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index adf7478f6..c336e10fe 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -52,7 +52,7 @@ func ShutdownNoOp() error { return nil } -// execHook executes all of the hooks for the given hook event and returns a shutdownHook function to trigger deletions after doing other things like e.g. retrieving logs. +// execHookWithDelayedShutdown executes all of the hooks for the given hook event and returns a shutdownHook function to trigger deletions after doing other things like e.g. retrieving logs. func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) (ExecuteShutdownHooks, error) { executingHooks := []*release.Hook{} From 9f1c8a26f00ebbe2942064a06b49c275162d10ef Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Wed, 26 Nov 2025 17:24:06 +0100 Subject: [PATCH 052/571] Fix linting issue Signed-off-by: Benoit Tigeot --- pkg/cmd/release_testing.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/cmd/release_testing.go b/pkg/cmd/release_testing.go index 30de160ed..5a6159e7d 100644 --- a/pkg/cmd/release_testing.go +++ b/pkg/cmd/release_testing.go @@ -75,7 +75,6 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command } }() - // We only return an error if we weren't even able to get the // release, otherwise we keep going so we can print status and logs // if requested From 6769fb6fb6704e29fe1215c802ecf0ea62b39715 Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Thu, 27 Nov 2025 00:11:20 +0530 Subject: [PATCH 053/571] generateName is also considered in logic Signed-off-by: Mujib Ahasan --- pkg/action/validate.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/action/validate.go b/pkg/action/validate.go index d89ae5038..555858fcf 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -45,9 +45,11 @@ func requireAdoption(resources kube.ResourceList) (kube.ResourceList, error) { if err != nil { return err } - - if info.Name == "" { + accessor, _ := meta.Accessor(info.Object) + if info.Name == "" && accessor.GetGenerateName() != "" { return nil + } else if info.Name != "" && accessor.GetGenerateName() != "" { + return fmt.Errorf("metadata.name and metadata.generateName cannot both be set") } helper := resource.NewHelper(info.Client, info.Mapping) @@ -74,8 +76,11 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN return err } - if info.Name == "" { + accessor, _ := meta.Accessor(info.Object) + if info.Name == "" && accessor.GetGenerateName() != "" { return nil + } else if info.Name != "" && accessor.GetGenerateName() != "" { + return fmt.Errorf("metadata.name and metadata.generateName cannot both be set") } helper := resource.NewHelper(info.Client, info.Mapping) From 906f87ce1d6d7a46bc68601c1217623e40b78877 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Wed, 26 Nov 2025 21:59:49 +0000 Subject: [PATCH 054/571] feat: add ownership verification before deleting resources during uninstall Fixes #31333 Helm uninstall now verifies resource ownership before deletion by checking Helm labels and annotations. Resources not owned by the release being uninstalled are skipped with warnings, preventing accidental deletion of resources belonging to other releases. Signed-off-by: Evans Mungai --- pkg/action/uninstall.go | 75 ++++++++++++++++++++++++++++-- pkg/action/validate.go | 49 ++++++++++++++++++++ pkg/action/validate_test.go | 92 +++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 4 deletions(-) diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index d5474490c..71db30a19 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -81,6 +81,46 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error) if err != nil { return nil, err } + + // Verify ownership in dry-run mode to show what would actually be deleted + manifests := releaseutil.SplitManifests(r.Manifest) + _, files, err := releaseutil.SortManifests(manifests, nil, releaseutil.UninstallOrder) + if err == nil { + filesToKeep, filesToDelete := filterManifestsToKeep(files) + + var builder strings.Builder + for _, file := range filesToDelete { + builder.WriteString("\n---\n" + file.Content) + } + + resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false) + if err == nil && len(resources) > 0 { + _, unownedResources, err := verifyOwnershipBeforeDelete(resources, r.Name, r.Namespace) + if err == nil && len(unownedResources) > 0 { + u.cfg.Logger().Warn("dry-run: resources would be skipped because they are not owned by this release", + "release", r.Name, + "count", len(unownedResources)) + for _, info := range unownedResources { + u.cfg.Logger().Warn("dry-run: would skip resource", + "kind", info.Mapping.GroupVersionKind.Kind, + "name", info.Name, + "namespace", info.Namespace) + } + } + } + + // Include kept resources in dry-run info + if len(filesToKeep) > 0 { + var kept strings.Builder + kept.WriteString("These resources were kept due to the resource policy:\n") + for _, f := range filesToKeep { + fmt.Fprintf(&kept, "[%s] %s\n", f.Head.Kind, f.Head.Metadata.Name) + } + res := &releasei.UninstallReleaseResponse{Release: r, Info: kept.String()} + return res, nil + } + } + return &releasei.UninstallReleaseResponse{Release: r}, nil } @@ -256,13 +296,40 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri if err != nil { return nil, "", []error{fmt.Errorf("unable to build kubernetes objects for delete: %w", err)} } + + // Verify ownership before deleting resources + var ownedResources, unownedResources kube.ResourceList if len(resources) > 0 { - _, errs = u.cfg.KubeClient.Delete(resources, parseCascadingFlag(u.DeletionPropagation)) + ownedResources, unownedResources, err = verifyOwnershipBeforeDelete(resources, rel.Name, rel.Namespace) + if err != nil { + return nil, "", []error{fmt.Errorf("unable to verify resource ownership: %w", err)} + } + + // Log warnings for unowned resources + if len(unownedResources) > 0 { + for _, info := range unownedResources { + u.cfg.Logger().Warn("skipping delete of resource not owned by this release", + "kind", info.Mapping.GroupVersionKind.Kind, + "name", info.Name, + "namespace", info.Namespace, + "release", rel.Name) + } + kept.WriteString(fmt.Sprintf("\n%d resource(s) were not deleted because they are not owned by this release:\n", len(unownedResources))) + for _, info := range unownedResources { + fmt.Fprintf(&kept, "[%s] %s\n", info.Mapping.GroupVersionKind.Kind, info.Name) + } + } + + // Delete only owned resources + if len(ownedResources) > 0 { + u.cfg.Logger().Debug("deleting resources part of this release", "count", len(ownedResources), "propagation", u.DeletionPropagation) + _, errs = u.cfg.KubeClient.Delete(ownedResources, parseCascadingFlag(u.DeletionPropagation, u.cfg.Logger())) + } } - return resources, kept.String(), errs + return ownedResources, kept.String(), errs } -func parseCascadingFlag(cascadingFlag string) v1.DeletionPropagation { +func parseCascadingFlag(cascadingFlag string, logger *slog.Logger) v1.DeletionPropagation { switch cascadingFlag { case "orphan": return v1.DeletePropagationOrphan @@ -271,7 +338,7 @@ func parseCascadingFlag(cascadingFlag string) v1.DeletionPropagation { case "background": return v1.DeletePropagationBackground default: - slog.Debug("uninstall: given cascade value, defaulting to delete propagation background", "value", cascadingFlag) + logger.Debug("uninstall: given cascade value, defaulting to delete propagation background", "value", cascadingFlag) return v1.DeletePropagationBackground } } diff --git a/pkg/action/validate.go b/pkg/action/validate.go index 1bef5a742..3a9d63253 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -93,6 +93,55 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN return requireUpdate, err } +// verifyOwnershipBeforeDelete checks that resources in the list are owned by the specified release. +// It returns two lists: owned resources (safe to delete) and unowned resources (should skip). +// Resources that are not found are considered owned (already deleted, safe to attempt delete). +func verifyOwnershipBeforeDelete(resources kube.ResourceList, releaseName, releaseNamespace string) (kube.ResourceList, kube.ResourceList, error) { + var owned kube.ResourceList + var unowned kube.ResourceList + + err := resources.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + + // If client is not available, skip verification (test scenario or build failure) + if info.Client == nil { + infoCopy := *info + owned.Append(&infoCopy) + return nil + } + + helper := resource.NewHelper(info.Client, info.Mapping) + existing, err := helper.Get(info.Namespace, info.Name) + if err != nil { + if apierrors.IsNotFound(err) { + // Resource already deleted, skip deletion + return nil + } + // Cannot fetch resource (network/permission issue), cannot verify ownership + infoCopy := *info + unowned.Append(&infoCopy) + return nil + } + + // Verify ownership of the existing resource + if err := checkOwnership(existing, releaseName, releaseNamespace); err != nil { + // Resource not owned by this release, cannot delete + infoCopy := *info + unowned.Append(&infoCopy) + return nil + } + + // Resource is owned by this release, can delete + infoCopy := *info + owned.Append(&infoCopy) + return nil + }) + + return owned, unowned, err +} + func checkOwnership(obj runtime.Object, releaseName, releaseNamespace string) error { lbls, err := accessor.Labels(obj) if err != nil { diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go index 879a5fa4f..d3e0edcde 100644 --- a/pkg/action/validate_test.go +++ b/pkg/action/validate_test.go @@ -210,6 +210,98 @@ func TestCheckOwnership(t *testing.T) { assert.EqualError(t, err, `invalid ownership metadata; label validation error: key "app.kubernetes.io/managed-by" must equal "Helm": current value is "helm"`) } +func TestVerifyOwnershipBeforeDelete(t *testing.T) { + var ( + releaseName = "rel-a" + releaseNamespace = "ns-a" + labels = map[string]string{ + appManagedByLabel: appManagedByHelm, + } + annotations = map[string]string{ + helmReleaseNameAnnotation: releaseName, + helmReleaseNamespaceAnnotation: releaseNamespace, + } + wrongAnnotations = map[string]string{ + helmReleaseNameAnnotation: "rel-b", + helmReleaseNamespaceAnnotation: releaseNamespace, + } + ) + + // Test all resources properly owned + t.Run("all resources owned", func(t *testing.T) { + owned1 := newDeploymentWithOwner("owned1", "ns-a", labels, annotations) + owned2 := newDeploymentWithOwner("owned2", "ns-a", labels, annotations) + resources := kube.ResourceList{owned1, owned2} + + ownedList, unownedList, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) + assert.NoError(t, err) + assert.Len(t, ownedList, 2) + assert.Len(t, unownedList, 0) + }) + + // Test mix of owned and unowned resources + t.Run("mixed ownership", func(t *testing.T) { + owned := newDeploymentWithOwner("owned", "ns-a", labels, annotations) + unowned := newDeploymentWithOwner("unowned", "ns-a", labels, wrongAnnotations) + resources := kube.ResourceList{owned, unowned} + + ownedList, unownedList, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) + assert.NoError(t, err) + assert.Len(t, ownedList, 1) + assert.Len(t, unownedList, 1) + assert.Equal(t, "owned", ownedList[0].Name) + assert.Equal(t, "unowned", unownedList[0].Name) + }) + + // Test resource not found (should be skipped - not in either list) + t.Run("resource not found", func(t *testing.T) { + missing := newMissingDeployment("missing", "ns-a") + resources := kube.ResourceList{missing} + + ownedList, unownedList, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) + assert.NoError(t, err) + assert.Len(t, ownedList, 0) + assert.Len(t, unownedList, 0) + }) + + // Test resource with no ownership metadata + t.Run("no ownership metadata", func(t *testing.T) { + noMeta := newDeploymentWithOwner("no-meta", "ns-a", nil, nil) + resources := kube.ResourceList{noMeta} + + ownedList, unownedList, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) + assert.NoError(t, err) + assert.Len(t, ownedList, 0) + assert.Len(t, unownedList, 1) + }) + + // Test resource owned by different release + t.Run("owned by different release", func(t *testing.T) { + otherRelease := newDeploymentWithOwner("other", "ns-a", labels, wrongAnnotations) + resources := kube.ResourceList{otherRelease} + + ownedList, unownedList, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) + assert.NoError(t, err) + assert.Len(t, ownedList, 0) + assert.Len(t, unownedList, 1) + }) + + // Test mixed scenario: owned, unowned, and missing resources + t.Run("mixed with missing resources", func(t *testing.T) { + owned := newDeploymentWithOwner("owned", "ns-a", labels, annotations) + unowned := newDeploymentWithOwner("unowned", "ns-a", labels, wrongAnnotations) + missing := newMissingDeployment("missing", "ns-a") + resources := kube.ResourceList{owned, unowned, missing} + + ownedList, unownedList, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) + assert.NoError(t, err) + assert.Len(t, ownedList, 1) + assert.Len(t, unownedList, 1) + assert.Equal(t, "owned", ownedList[0].Name) + assert.Equal(t, "unowned", unownedList[0].Name) + }) +} + func TestSetMetadataVisitor(t *testing.T) { var ( err error From 6d5b5aab32be039f8f56b0c497a7f4af28ce21f0 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Wed, 26 Nov 2025 22:22:57 +0000 Subject: [PATCH 055/571] Additional logging for ownership verification in dry-run mode Signed-off-by: Evans Mungai --- pkg/action/uninstall.go | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index 71db30a19..7e78f820e 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -95,16 +95,30 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error) resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false) if err == nil && len(resources) > 0 { - _, unownedResources, err := verifyOwnershipBeforeDelete(resources, r.Name, r.Namespace) - if err == nil && len(unownedResources) > 0 { - u.cfg.Logger().Warn("dry-run: resources would be skipped because they are not owned by this release", - "release", r.Name, - "count", len(unownedResources)) - for _, info := range unownedResources { - u.cfg.Logger().Warn("dry-run: would skip resource", - "kind", info.Mapping.GroupVersionKind.Kind, - "name", info.Name, - "namespace", info.Namespace) + ownedResources, unownedResources, err := verifyOwnershipBeforeDelete(resources, r.Name, r.Namespace) + if err == nil { + if len(unownedResources) > 0 { + u.cfg.Logger().Warn("dry-run: resources would be skipped because they are not owned by this release", + "release", r.Name, + "count", len(unownedResources)) + for _, info := range unownedResources { + u.cfg.Logger().Warn("dry-run: would skip resource", + "kind", info.Mapping.GroupVersionKind.Kind, + "name", info.Name, + "namespace", info.Namespace) + } + } + + if len(ownedResources) > 0 { + u.cfg.Logger().Debug("dry-run: resources would be deleted", + "release", r.Name, + "count", len(ownedResources)) + for _, info := range ownedResources { + u.cfg.Logger().Debug("dry-run: would delete resource", + "kind", info.Mapping.GroupVersionKind.Kind, + "name", info.Name, + "namespace", info.Namespace) + } } } } From 70fc5f97e2b42a2cf87ff8756d6baf4ab2d5a920 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Wed, 26 Nov 2025 23:29:57 +0100 Subject: [PATCH 056/571] Code review Signed-off-by: Benoit Tigeot --- pkg/action/hooks.go | 16 ++++++++-------- pkg/action/release_testing.go | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index c336e10fe..2ea851a3e 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -35,7 +35,7 @@ import ( func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) error { shutdown, err := cfg.execHookWithDelayedShutdown(rl, hook, waitStrategy, timeout, serverSideApply) if shutdown == nil { - return nil + return err } if err != nil { if err := shutdown(); err != nil { @@ -46,14 +46,14 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, return shutdown() } -type ExecuteShutdownHooks = func() error +type executeShutdownFunc = func() error -func ShutdownNoOp() error { +func shutdownNoOp() error { return nil } // execHookWithDelayedShutdown executes all of the hooks for the given hook event and returns a shutdownHook function to trigger deletions after doing other things like e.g. retrieving logs. -func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) (ExecuteShutdownHooks, error) { +func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) (executeShutdownFunc, error) { executingHooks := []*release.Hook{} for _, h := range rl.Hooks { @@ -72,12 +72,12 @@ func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook cfg.hookSetDeletePolicy(h) if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, waitStrategy, timeout); err != nil { - return ShutdownNoOp, err + return shutdownNoOp, err } resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), true) if err != nil { - return ShutdownNoOp, fmt.Errorf("unable to build kubernetes object for %s hook %s: %w", hook, h.Path, err) + return shutdownNoOp, fmt.Errorf("unable to build kubernetes object for %s hook %s: %w", hook, h.Path, err) } // Record the time at which the hook was applied to the cluster @@ -98,12 +98,12 @@ func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook kube.ClientCreateOptionServerSideApply(serverSideApply, false)); err != nil { h.LastRun.CompletedAt = time.Now() h.LastRun.Phase = release.HookPhaseFailed - return ShutdownNoOp, fmt.Errorf("warning: Hook %s %s failed: %w", hook, h.Path, err) + return shutdownNoOp, fmt.Errorf("warning: Hook %s %s failed: %w", hook, h.Path, err) } waiter, err := cfg.KubeClient.GetWaiter(waitStrategy) if err != nil { - return ShutdownNoOp, fmt.Errorf("unable to get waiter: %w", err) + return shutdownNoOp, fmt.Errorf("unable to get waiter: %w", err) } // Watch hook resources until they have completed err = waiter.WatchUntilReady(resources, timeout) diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index 2aa063515..673a678f5 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -57,24 +57,24 @@ func NewReleaseTesting(cfg *Configuration) *ReleaseTesting { } // Run executes 'helm test' against the given release. -func (r *ReleaseTesting) Run(name string) (ri.Releaser, ExecuteShutdownHooks, error) { +func (r *ReleaseTesting) Run(name string) (ri.Releaser, executeShutdownFunc, error) { if err := r.cfg.KubeClient.IsReachable(); err != nil { - return nil, ShutdownNoOp, err + return nil, shutdownNoOp, err } if err := chartutil.ValidateReleaseName(name); err != nil { - return nil, ShutdownNoOp, fmt.Errorf("releaseTest: Release name is invalid: %s", name) + return nil, shutdownNoOp, fmt.Errorf("releaseTest: Release name is invalid: %s", name) } // finds the non-deleted release with the given name reli, err := r.cfg.Releases.Last(name) if err != nil { - return reli, ShutdownNoOp, err + return reli, shutdownNoOp, err } rel, err := releaserToV1Release(reli) if err != nil { - return reli, ShutdownNoOp, err + return reli, shutdownNoOp, err } skippedHooks := []*release.Hook{} From b33d4ae0c07c37436c00c8ba13ca7a0ea9f6aa19 Mon Sep 17 00:00:00 2001 From: Calvin Bui <3604363+calvinbui@users.noreply.github.com> Date: Thu, 27 Nov 2025 16:56:17 +1100 Subject: [PATCH 057/571] Use length check for MetaDependencies instead of nil comparison Signed-off-by: Calvin Bui <3604363+calvinbui@users.noreply.github.com> --- pkg/action/package.go | 2 +- pkg/cmd/install.go | 2 +- pkg/cmd/upgrade.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/action/package.go b/pkg/action/package.go index 963883707..0ab49538c 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -103,7 +103,7 @@ func (p *Package) Run(path string, _ map[string]interface{}) (string, error) { ch.Metadata.AppVersion = p.AppVersion } - if reqs := ac.MetaDependencies(); reqs != nil { + if reqs := ac.MetaDependencies(); len(reqs) > 0 { if err := CheckDependencies(ch, reqs); err != nil { return "", err } diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index e5d311394..d36cd9e34 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -275,7 +275,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options slog.Warn("this chart is deprecated") } - if req := ac.MetaDependencies(); req != nil { + if req := ac.MetaDependencies(); len(req) > 0 { // If CheckDependencies returns an error, we have unfulfilled dependencies. // As of Helm 2.4.0, this is treated as a stopping condition: // https://github.com/helm/helm/issues/2209 diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index 92f130f60..bcb4b048f 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -200,7 +200,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if err != nil { return err } - if req := ac.MetaDependencies(); req != nil { + if req := ac.MetaDependencies(); len(req) > 0 { if err := action.CheckDependencies(ch, req); err != nil { err = fmt.Errorf("an error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: %w", err) if client.DependencyUpdate { From 45c5f3aaca1a37d8388ce7b79efe2dfaf84dcdcc Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Thu, 27 Nov 2025 08:48:31 +0100 Subject: [PATCH 058/571] Deal with golint warning with private executeShutdownFunc ``` Error: pkg/action/release_testing.go:60:57: unexported-return: exported method Run returns unexported type action.executeShutdownFunc, which can be annoying to use (revive) func (r *ReleaseTesting) Run(name string) (ri.Releaser, executeShutdownFunc, error) { ``` Signed-off-by: Benoit Tigeot --- pkg/action/hooks.go | 4 ++-- pkg/action/release_testing.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index 2ea851a3e..49849a27d 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -46,14 +46,14 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, return shutdown() } -type executeShutdownFunc = func() error +type ExecuteShutdownFunc = func() error func shutdownNoOp() error { return nil } // execHookWithDelayedShutdown executes all of the hooks for the given hook event and returns a shutdownHook function to trigger deletions after doing other things like e.g. retrieving logs. -func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) (executeShutdownFunc, error) { +func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) (ExecuteShutdownFunc, error) { executingHooks := []*release.Hook{} for _, h := range rl.Hooks { diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index 673a678f5..992cdd701 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -57,7 +57,7 @@ func NewReleaseTesting(cfg *Configuration) *ReleaseTesting { } // Run executes 'helm test' against the given release. -func (r *ReleaseTesting) Run(name string) (ri.Releaser, executeShutdownFunc, error) { +func (r *ReleaseTesting) Run(name string) (ri.Releaser, ExecuteShutdownFunc, error) { if err := r.cfg.KubeClient.IsReachable(); err != nil { return nil, shutdownNoOp, err } From faa8912e36bfebf45735fbd6cbd709181b311d22 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Thu, 27 Nov 2025 08:55:42 +0100 Subject: [PATCH 059/571] Inform we use a different golangci-lint version than the CI Signed-off-by: Benoit Tigeot --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index d7422bf23..25b8a56f6 100644 --- a/Makefile +++ b/Makefile @@ -129,6 +129,13 @@ test-coverage: .PHONY: test-style test-style: + @EXPECTED_VERSION=$$(grep GOLANGCI_LINT_VERSION .github/env | cut -d= -f2); \ + ACTUAL_VERSION=$$(golangci-lint --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1); \ + if [ "v$$ACTUAL_VERSION" != "$$EXPECTED_VERSION" ]; then \ + echo "Warning: golangci-lint version is v$$ACTUAL_VERSION (expected $$EXPECTED_VERSION from CI)"; \ + echo "To install the correct version, run:"; \ + echo " curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b \$$(go env GOPATH)/bin $$EXPECTED_VERSION"; \ + fi golangci-lint run ./... @scripts/validate-license.sh From f552950b05066d4d6a9d6cf9a91c26656f4a90fc Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Thu, 27 Nov 2025 15:32:37 +0000 Subject: [PATCH 060/571] Add ownership verification to uninstall action This commit adds ownership verification to the uninstall action. It checks that the resource to be deleted is owned by the chart before deleting it. Fixes: #31333 Signed-off-by: Evans Mungai --- pkg/action/uninstall.go | 8 +- pkg/action/uninstall_test.go | 186 ++++++++++++++++++++++++++++++++++- 2 files changed, 192 insertions(+), 2 deletions(-) diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index 7e78f820e..c1e5b00e7 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -336,7 +336,13 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri // Delete only owned resources if len(ownedResources) > 0 { - u.cfg.Logger().Debug("deleting resources part of this release", "count", len(ownedResources), "propagation", u.DeletionPropagation) + for _, info := range ownedResources { + u.cfg.Logger().Debug("deleting resource owned by this release", + "kind", info.Mapping.GroupVersionKind.Kind, + "name", info.Name, + "namespace", info.Namespace, + "release", rel.Name) + } _, errs = u.cfg.KubeClient.Delete(ownedResources, parseCascadingFlag(u.DeletionPropagation, u.cfg.Logger())) } } diff --git a/pkg/action/uninstall_test.go b/pkg/action/uninstall_test.go index fba1e391f..bed9c04ac 100644 --- a/pkg/action/uninstall_test.go +++ b/pkg/action/uninstall_test.go @@ -17,9 +17,11 @@ limitations under the License. package action import ( + "bytes" "errors" "fmt" "io" + "log/slog" "testing" "github.com/stretchr/testify/assert" @@ -147,9 +149,16 @@ func TestUninstallRelease_Cascade(t *testing.T) { } }` unAction.cfg.Releases.Create(rel) + + // Create dummy resources with Mapping but no Client - this skips ownership verification + // (nil Client is treated as owned) and goes directly to delete + dummyResources := kube.ResourceList{ + newDeploymentResource("secret", ""), + } + failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.DeleteError = fmt.Errorf("Uninstall with cascade failed") - failer.BuildDummy = true + failer.DummyResources = dummyResources unAction.cfg.KubeClient = failer _, err := unAction.Run(rel.Name) require.Error(t, err) @@ -169,3 +178,178 @@ func TestUninstallRun_UnreachableKubeClient(t *testing.T) { assert.Nil(t, result) assert.ErrorContains(t, err, "connection refused") } + +func TestUninstallRelease_OwnershipVerification(t *testing.T) { + is := assert.New(t) + + // Create a buffer to capture log output + logBuffer := &bytes.Buffer{} + handler := slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}) + + config := actionConfigFixture(t) + config.SetLogger(handler) + + unAction := NewUninstall(config) + unAction.DisableHooks = true + unAction.DryRun = false + unAction.KeepHistory = true + + rel := releaseStub() + rel.Name = "ownership-test" + rel.Namespace = "default" + rel.Manifest = `apiVersion: v1 +kind: ConfigMap +metadata: + name: test-configmap + labels: + app.kubernetes.io/managed-by: Helm + annotations: + meta.helm.sh/release-name: ownership-test + meta.helm.sh/release-namespace: default +data: + key: value` + config.Releases.Create(rel) + + // Create dummy resources with proper ownership metadata + labels := map[string]string{ + "app.kubernetes.io/managed-by": "Helm", + } + annotations := map[string]string{ + "meta.helm.sh/release-name": "ownership-test", + "meta.helm.sh/release-namespace": "default", + } + dummyResources := kube.ResourceList{ + newDeploymentWithOwner("owned-deploy", "default", labels, annotations), + } + failer := config.KubeClient.(*kubefake.FailingKubeClient) + failer.DummyResources = dummyResources + + resi, err := unAction.Run(rel.Name) + is.NoError(err) + is.NotNil(resi) + res, err := releaserToV1Release(resi.Release) + is.NoError(err) + is.Equal(common.StatusUninstalled, res.Info.Status) + + // Verify log contains debug message about deleting owned resource + logOutput := logBuffer.String() + is.Contains(logOutput, "deleting resource owned by this release") + is.Contains(logOutput, "owned-deploy") + is.Contains(logOutput, "Deployment") +} + +func TestUninstallRelease_OwnershipVerification_WithKeepPolicy(t *testing.T) { + is := assert.New(t) + + // Create a buffer to capture log output + logBuffer := &bytes.Buffer{} + handler := slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelWarn}) + + config := actionConfigFixture(t) + config.SetLogger(handler) + + unAction := NewUninstall(config) + unAction.DisableHooks = true + unAction.DryRun = false + unAction.KeepHistory = true + + rel := releaseStub() + rel.Name = "keep-and-ownership" + rel.Namespace = "default" + rel.Manifest = `apiVersion: v1 +kind: Secret +metadata: + name: kept-secret + annotations: + helm.sh/resource-policy: keep + meta.helm.sh/release-name: keep-and-ownership + meta.helm.sh/release-namespace: default + labels: + app.kubernetes.io/managed-by: Helm +type: Opaque +data: + password: cGFzc3dvcmQ= +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: deleted-configmap + labels: + app.kubernetes.io/managed-by: Helm + annotations: + meta.helm.sh/release-name: keep-and-ownership + meta.helm.sh/release-namespace: default +data: + key: value` + config.Releases.Create(rel) + + // Create dummy resources - one unowned to test logging + dummyResources := kube.ResourceList{ + newDeploymentWithOwner("unowned-deploy", "default", nil, nil), + } + failer := config.KubeClient.(*kubefake.FailingKubeClient) + failer.DummyResources = dummyResources + + res, err := unAction.Run(rel.Name) + is.NoError(err) + is.NotNil(res) + // Should contain info about kept resources + is.Contains(res.Info, "kept due to the resource policy") + + // Verify log contains warning about skipped unowned resource + logOutput := logBuffer.String() + is.Contains(logOutput, "skipping delete of resource not owned by this release") + is.Contains(logOutput, "unowned-deploy") +} + +func TestUninstallRelease_DryRun_OwnershipVerification(t *testing.T) { + is := assert.New(t) + + // Create a buffer to capture log output + logBuffer := &bytes.Buffer{} + handler := slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelWarn}) + + config := actionConfigFixture(t) + config.SetLogger(handler) + + unAction := NewUninstall(config) + unAction.DisableHooks = true + unAction.DryRun = true + + rel := releaseStub() + rel.Name = "dryrun-ownership" + rel.Namespace = "default" + rel.Manifest = `apiVersion: v1 +kind: ConfigMap +metadata: + name: test-configmap + labels: + app.kubernetes.io/managed-by: Helm + annotations: + meta.helm.sh/release-name: dryrun-ownership + meta.helm.sh/release-namespace: default +data: + key: value` + config.Releases.Create(rel) + + // Create dummy resources - one unowned to test dry-run logging + dummyResources := kube.ResourceList{ + newDeploymentWithOwner("dryrun-unowned-deploy", "default", nil, nil), + } + failer := config.KubeClient.(*kubefake.FailingKubeClient) + failer.DummyResources = dummyResources + + resi, err := unAction.Run(rel.Name) + is.NoError(err) + is.NotNil(resi) + is.NotNil(resi.Release) + res, err := releaserToV1Release(resi.Release) + is.NoError(err) + is.Equal("dryrun-ownership", res.Name) + + // Verify log contains dry-run warning about resources that would be skipped + logOutput := logBuffer.String() + is.Contains(logOutput, "dry-run: would skip resource") + is.Contains(logOutput, "dryrun-unowned-deploy") + is.Contains(logOutput, "Deployment") +} From 522d2fe61508639cfe8f06a43235e7c3eaea3b9a Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Sun, 30 Nov 2025 22:46:57 +0530 Subject: [PATCH 061/571] update: helper function added for the business logic Signed-off-by: Mujib Ahasan --- pkg/action/validate.go | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/pkg/action/validate.go b/pkg/action/validate.go index 555858fcf..045a70955 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -45,11 +45,10 @@ func requireAdoption(resources kube.ResourceList) (kube.ResourceList, error) { if err != nil { return err } - accessor, _ := meta.Accessor(info.Object) - if info.Name == "" && accessor.GetGenerateName() != "" { - return nil - } else if info.Name != "" && accessor.GetGenerateName() != "" { - return fmt.Errorf("metadata.name and metadata.generateName cannot both be set") + + skip, err := validateNameAndgenerateName(info) + if skip { + return err } helper := resource.NewHelper(info.Client, info.Mapping) @@ -76,11 +75,9 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN return err } - accessor, _ := meta.Accessor(info.Object) - if info.Name == "" && accessor.GetGenerateName() != "" { - return nil - } else if info.Name != "" && accessor.GetGenerateName() != "" { - return fmt.Errorf("metadata.name and metadata.generateName cannot both be set") + skip, err := validateNameAndgenerateName(info) + if skip { + return err } helper := resource.NewHelper(info.Client, info.Mapping) @@ -212,3 +209,14 @@ func mergeStrStrMaps(current, desired map[string]string) map[string]string { maps.Copy(result, desired) return result } + +func validateNameAndgenerateName(info *resource.Info) (bool, error) { + accessor, _ := meta.Accessor(info.Object) + if info.Name == "" && accessor.GetGenerateName() != "" { + return true, nil + } else if info.Name != "" && accessor.GetGenerateName() != "" { + return true, fmt.Errorf("metadata.name and metadata.generateName cannot both be set") + } + + return false, nil +} From 115409976b5c3fd94c893eabde114e655c01c573 Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Sun, 30 Nov 2025 22:49:20 +0530 Subject: [PATCH 062/571] update: test coverage added for helper function validateNameAndGenerateName Signed-off-by: Mujib Ahasan --- pkg/action/validate_test.go | 61 ++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go index 3efecd6ff..32ee8d802 100644 --- a/pkg/action/validate_test.go +++ b/pkg/action/validate_test.go @@ -36,7 +36,7 @@ import ( "k8s.io/client-go/rest/fake" ) -func newDeploymentResource(name, namespace string) *resource.Info { +func newDeploymentResource(name, namespace, generateName string) *resource.Info { return &resource.Info{ Name: name, Mapping: &meta.RESTMapping{ @@ -45,8 +45,9 @@ func newDeploymentResource(name, namespace string) *resource.Info { }, Object: &appsv1.Deployment{ ObjectMeta: v1.ObjectMeta{ - Name: name, - Namespace: namespace, + Name: name, + Namespace: namespace, + GenerateName: generateName, }, }, } @@ -164,7 +165,7 @@ func TestExistingResourceConflict(t *testing.T) { } func TestCheckOwnership(t *testing.T) { - deployFoo := newDeploymentResource("foo", "ns-a") + deployFoo := newDeploymentResource("foo", "ns-a", "") // Verify that a resource that lacks labels/annotations is not owned err := checkOwnership(deployFoo.Object, "rel-a", "ns-a") @@ -211,8 +212,8 @@ func TestCheckOwnership(t *testing.T) { func TestSetMetadataVisitor(t *testing.T) { var ( err error - deployFoo = newDeploymentResource("foo", "ns-a") - deployBar = newDeploymentResource("bar", "ns-a-system") + deployFoo = newDeploymentResource("foo", "ns-a", "") + deployBar = newDeploymentResource("bar", "ns-a-system", "") resources = kube.ResourceList{deployFoo, deployBar} ) @@ -233,8 +234,54 @@ func TestSetMetadataVisitor(t *testing.T) { assert.NoError(t, err) // Add a new resource that is missing ownership metadata and verify error - resources.Append(newDeploymentResource("baz", "default")) + resources.Append(newDeploymentResource("baz", "default", "")) err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) assert.Error(t, err) assert.Contains(t, err.Error(), `Deployment "baz" in namespace "" cannot be owned`) } + +func TestValidateNameAndGenerateName(t *testing.T) { + tests := []struct { + name string + info *resource.Info + wantSkip bool + wantErr bool + errContains string + }{ + { + name: "both name and generateName present", + info: newDeploymentResource("job-a", "foo", "job-a-"), + wantSkip: true, + wantErr: true, + errContains: "metadata.name and metadata.generateName cannot both be set", + }, + { + name: "only generateName present", + info: newDeploymentResource("", "foo", "job-a-"), + wantSkip: true, + wantErr: false, + }, + { + name: "only name present", + info: newDeploymentResource("job-a", "foo", ""), + wantSkip: false, + wantErr: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + + skip, err := validateNameAndgenerateName(tc.info) + + if tc.wantErr { + assert.Error(t, err) + assert.Contains(t, err.Error(), tc.errContains) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, tc.wantSkip, skip) + }) + } +} From e6b2068afd7ad5d86b98b69ae35840ebef680002 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 21:07:52 +0000 Subject: [PATCH 063/571] chore(deps): bump actions/checkout from 6.0.0 to 6.0.1 Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/1af3b93b6815bc44a9784bd300feb67ff0d1eeb3...8e8c483db84b4bee98b60c0593521ed34d9990e8) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 3ca60e0fa..54fa3a078 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # pin@v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 5c52b86c4..0f3fe6d8f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -43,7 +43,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # pin@v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index b9d376519..94dbef8b5 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # pin@v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index b245f85ca..a3088ad75 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # pin@v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ae5839b2..34b244086 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest-16-cores steps: - name: Checkout source code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # pin@v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 with: fetch-depth: 0 @@ -84,7 +84,7 @@ jobs: if: github.ref == 'refs/heads/main' && github.repository == 'helm/helm' steps: - name: Checkout source code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # pin@v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index aad2594f4..3e475edc0 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -28,7 +28,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false From 31131cff9ba3c52060c4b999fa291eaf64abd51d Mon Sep 17 00:00:00 2001 From: Stephanie Hohenberg Date: Wed, 3 Sep 2025 19:07:45 -0500 Subject: [PATCH 064/571] Add tests to action package to improve coverage Signed-off-by: Stephanie Hohenberg --- pkg/action/action_test.go | 6 ++ pkg/action/get_metadata_test.go | 16 +-- pkg/action/get_test.go | 66 +++++++++++++ pkg/action/get_values_test.go | 8 +- pkg/action/history_test.go | 101 +++++++++++++++++++ pkg/action/install.go | 2 +- pkg/action/install_test.go | 131 ++++++++++++++++++++++++- pkg/action/lint_test.go | 47 +++++++++ pkg/action/package_test.go | 19 +++- pkg/action/pull_test.go | 80 +++++++++++++++ pkg/action/push_test.go | 66 +++++++++++++ pkg/action/registry_login_test.go | 84 ++++++++++++++++ pkg/action/registry_logout_test.go | 31 ++++++ pkg/action/release_testing_test.go | 91 +++++++++++++++++ pkg/action/rollback_test.go | 45 +++++++++ pkg/action/show_test.go | 12 +++ pkg/action/status_test.go | 140 +++++++++++++++++++++++++++ pkg/action/uninstall_test.go | 6 +- pkg/action/upgrade_test.go | 46 ++++++--- pkg/action/verify_test.go | 42 ++++++++ pkg/kube/fake/failing_kube_client.go | 3 + 21 files changed, 1010 insertions(+), 32 deletions(-) create mode 100644 pkg/action/get_test.go create mode 100644 pkg/action/history_test.go create mode 100644 pkg/action/pull_test.go create mode 100644 pkg/action/push_test.go create mode 100644 pkg/action/registry_login_test.go create mode 100644 pkg/action/registry_logout_test.go create mode 100644 pkg/action/release_testing_test.go create mode 100644 pkg/action/rollback_test.go create mode 100644 pkg/action/status_test.go create mode 100644 pkg/action/verify_test.go diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index b77a462f5..85ee42d64 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -201,6 +201,12 @@ func withMetadataDependency(dependency chart.Dependency) chartOption { } } +func withFile(file common.File) chartOption { + return func(opts *chartOptions) { + opts.Files = append(opts.Files, &file) + } +} + func withSampleTemplates() chartOption { return func(opts *chartOptions) { modTime := time.Now() diff --git a/pkg/action/get_metadata_test.go b/pkg/action/get_metadata_test.go index cd5988d8e..4caa966ab 100644 --- a/pkg/action/get_metadata_test.go +++ b/pkg/action/get_metadata_test.go @@ -121,7 +121,7 @@ func TestGetMetadata_Run_WithDependencies(t *testing.T) { Namespace: "default", } - cfg.Releases.Create(rel) + require.NoError(t, cfg.Releases.Create(rel)) result, err := client.Run(releaseName) require.NoError(t, err) @@ -180,7 +180,7 @@ func TestGetMetadata_Run_WithDependenciesAliases(t *testing.T) { Namespace: "default", } - cfg.Releases.Create(rel) + require.NoError(t, cfg.Releases.Create(rel)) result, err := client.Run(releaseName) require.NoError(t, err) @@ -251,7 +251,7 @@ func TestGetMetadata_Run_WithMixedDependencies(t *testing.T) { Namespace: "default", } - cfg.Releases.Create(rel) + require.NoError(t, cfg.Releases.Create(rel)) result, err := client.Run(releaseName) require.NoError(t, err) @@ -315,7 +315,7 @@ func TestGetMetadata_Run_WithAnnotations(t *testing.T) { Namespace: "default", } - cfg.Releases.Create(rel) + require.NoError(t, cfg.Releases.Create(rel)) result, err := client.Run(releaseName) require.NoError(t, err) @@ -370,8 +370,8 @@ func TestGetMetadata_Run_SpecificVersion(t *testing.T) { Namespace: "default", } - cfg.Releases.Create(rel1) - cfg.Releases.Create(rel2) + require.NoError(t, cfg.Releases.Create(rel1)) + require.NoError(t, cfg.Releases.Create(rel2)) result, err := client.Run(releaseName) require.NoError(t, err) @@ -424,7 +424,7 @@ func TestGetMetadata_Run_DifferentStatuses(t *testing.T) { Namespace: "default", } - cfg.Releases.Create(rel) + require.NoError(t, cfg.Releases.Create(rel)) result, err := client.Run(releaseName) require.NoError(t, err) @@ -480,7 +480,7 @@ func TestGetMetadata_Run_EmptyAppVersion(t *testing.T) { Namespace: "default", } - cfg.Releases.Create(rel) + require.NoError(t, cfg.Releases.Create(rel)) result, err := client.Run(releaseName) require.NoError(t, err) diff --git a/pkg/action/get_test.go b/pkg/action/get_test.go new file mode 100644 index 000000000..53a0645eb --- /dev/null +++ b/pkg/action/get_test.go @@ -0,0 +1,66 @@ +/* +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 action + +import ( + "errors" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + kubefake "helm.sh/helm/v4/pkg/kube/fake" + release "helm.sh/helm/v4/pkg/release/v1" +) + +func TestNewGet(t *testing.T) { + config := actionConfigFixture(t) + client := NewGet(config) + + assert.NotNil(t, client) + assert.Equal(t, config, client.cfg) + assert.Equal(t, 0, client.Version) +} + +func TestGetRun(t *testing.T) { + config := actionConfigFixture(t) + client := NewGet(config) + simpleRelease := namedReleaseStub("test-release", release.StatusPendingUpgrade) + require.NoError(t, config.Releases.Create(simpleRelease)) + + result, err := client.Run(simpleRelease.Name) + + require.NoError(t, err) + assert.Equal(t, simpleRelease.Name, result.Name) + assert.Equal(t, simpleRelease.Version, result.Version) +} + +func TestGetRun_UnreachableKubeClient(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.ConnectionError = errors.New("connection refused") + config.KubeClient = &failingKubeClient + + client := NewGet(config) + simpleRelease := namedReleaseStub("test-release", release.StatusPendingUpgrade) + require.NoError(t, config.Releases.Create(simpleRelease)) + + result, err := client.Run(simpleRelease.Name) + assert.Nil(t, result) + assert.Error(t, err) +} diff --git a/pkg/action/get_values_test.go b/pkg/action/get_values_test.go index 8e6588454..69a95a2e4 100644 --- a/pkg/action/get_values_test.go +++ b/pkg/action/get_values_test.go @@ -79,7 +79,7 @@ func TestGetValues_Run_UserConfigOnly(t *testing.T) { Namespace: "default", } - cfg.Releases.Create(rel) + require.NoError(t, cfg.Releases.Create(rel)) result, err := client.Run(releaseName) require.NoError(t, err) @@ -127,7 +127,7 @@ func TestGetValues_Run_AllValues(t *testing.T) { Namespace: "default", } - cfg.Releases.Create(rel) + require.NoError(t, cfg.Releases.Create(rel)) result, err := client.Run(releaseName) require.NoError(t, err) @@ -161,7 +161,7 @@ func TestGetValues_Run_EmptyValues(t *testing.T) { Namespace: "default", } - cfg.Releases.Create(rel) + require.NoError(t, cfg.Releases.Create(rel)) result, err := client.Run(releaseName) require.NoError(t, err) @@ -212,7 +212,7 @@ func TestGetValues_Run_NilConfig(t *testing.T) { Namespace: "default", } - cfg.Releases.Create(rel) + require.NoError(t, cfg.Releases.Create(rel)) result, err := client.Run(releaseName) require.NoError(t, err) diff --git a/pkg/action/history_test.go b/pkg/action/history_test.go new file mode 100644 index 000000000..31c2c87d0 --- /dev/null +++ b/pkg/action/history_test.go @@ -0,0 +1,101 @@ +/* +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 action + +import ( + "errors" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + kubefake "helm.sh/helm/v4/pkg/kube/fake" + release "helm.sh/helm/v4/pkg/release/v1" +) + +func TestNewHistory(t *testing.T) { + config := actionConfigFixture(t) + client := NewHistory(config) + + assert.NotNil(t, client) + assert.Equal(t, config, client.cfg) +} + +func TestHistoryRun(t *testing.T) { + releaseName := "test-release" + simpleRelease := namedReleaseStub(releaseName, release.StatusPendingUpgrade) + updatedRelease := namedReleaseStub(releaseName, release.StatusDeployed) + updatedRelease.Chart.Metadata.Version = "0.1.1" + updatedRelease.Version = 2 + + config := actionConfigFixture(t) + client := NewHistory(config) + client.Max = 3 + client.cfg.Releases.MaxHistory = 3 + for _, rel := range []*release.Release{simpleRelease, updatedRelease} { + if err := client.cfg.Releases.Create(rel); err != nil { + t.Fatal(err, "Could not add releases to Config") + } + } + + releases, err := config.Releases.ListReleases() + require.NoError(t, err) + assert.Len(t, releases, 2, "expected 2 Releases in Config") + + result, err := client.Run(releaseName) + require.NoError(t, err) + + assert.Len(t, result, 2, "expected 2 Releases in History result") + assert.Equal(t, simpleRelease.Name, result[0].Name) + assert.Equal(t, simpleRelease.Version, result[0].Version) + assert.Equal(t, updatedRelease.Name, result[1].Name) + assert.Equal(t, updatedRelease.Version, result[1].Version) +} + +func TestHistoryRun_UnreachableKubeClient(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.ConnectionError = errors.New("connection refused") + config.KubeClient = &failingKubeClient + + client := NewHistory(config) + result, err := client.Run("release-name") + assert.Nil(t, result) + assert.Error(t, err) +} + +func TestHistoryRun_InvalidReleaseNames(t *testing.T) { + config := actionConfigFixture(t) + client := NewHistory(config) + invalidReleaseNames := []string{ + "", + "too-long-release-name-max-53-characters-abcdefghijklmnopqrstuvwxyz", + "MyRelease", + "release_name", + "release@123", + "-badstart", + "badend-", + ".dotstart", + } + + for _, name := range invalidReleaseNames { + result, err := client.Run(name) + assert.Nil(t, result) + assert.ErrorContains(t, err, "release name is invalid") + } +} diff --git a/pkg/action/install.go b/pkg/action/install.go index 2f5910284..3ec11e52f 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -192,7 +192,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error { kube.ClientCreateOptionServerSideApply(i.ServerSideApply, i.ForceConflicts)); err != nil { // If the error is CRD already exists, continue. if apierrors.IsAlreadyExists(err) { - crdName := res[0].Name + crdName := obj.Name i.cfg.Logger().Debug("CRD is already present. Skipping", "crd", crdName) continue } diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 9f04f40d4..41d56152e 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -35,6 +35,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kuberuntime "k8s.io/apimachinery/pkg/runtime" @@ -45,9 +46,11 @@ import ( "helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/pkg/chart/common" + chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/kube" kubefake "helm.sh/helm/v4/pkg/kube/fake" rcommon "helm.sh/helm/v4/pkg/release/common" + "helm.sh/helm/v4/pkg/registry" release "helm.sh/helm/v4/pkg/release/v1" "helm.sh/helm/v4/pkg/storage/driver" ) @@ -272,6 +275,17 @@ func TestInstallReleaseWithValues(t *testing.T) { is.Equal(expectedUserValues, rel.Config) } +func TestInstallReleaseClientOnly(t *testing.T) { + is := assert.New(t) + instAction := installAction(t) + instAction.ClientOnly = true + _, err := instAction.Run(buildChart(), nil) + require.NoError(t, err) + + is.Equal(instAction.cfg.Capabilities, common.DefaultCapabilities) + is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: io.Discard}) +} + func TestInstallRelease_NoName(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "" @@ -500,7 +514,7 @@ func TestInstallRelease_NoHooks(t *testing.T) { instAction := installAction(t) instAction.DisableHooks = true instAction.ReleaseName = "no-hooks" - instAction.cfg.Releases.Create(releaseStub()) + require.NoError(t, instAction.cfg.Releases.Create(releaseStub())) vals := map[string]interface{}{} resi, err := instAction.Run(buildChart(), vals) @@ -540,7 +554,7 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) { rel := releaseStub() rel.Info.Status = rcommon.StatusUninstalled - instAction.cfg.Releases.Create(rel) + require.NoError(t, instAction.cfg.Releases.Create(rel)) instAction.ReleaseName = rel.Name vals := map[string]interface{}{} @@ -1068,3 +1082,116 @@ func TestInstallRun_UnreachableKubeClient(t *testing.T) { assert.Nil(t, res) assert.ErrorContains(t, err, "connection refused") } + +func TestInstallSetRegistryClient(t *testing.T) { + config := actionConfigFixture(t) + instAction := NewInstall(config) + + registryClient := ®istry.Client{} + instAction.SetRegistryClient(registryClient) + + assert.Equal(t, registryClient, instAction.GetRegistryClient()) +} + +func TestInstalLCRDs(t *testing.T) { + config := actionConfigFixture(t) + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + assert.Len(t, crdsToInstall, 1) + assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) + + require.NoError(t, instAction.installCRDs(crdsToInstall)) +} + +func TestInstalLCRDs_KubeClient_BuildError(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.BuildError = errors.New("build error") + config.KubeClient = &failingKubeClient + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + + require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") +} + +func TestInstalLCRDs_KubeClient_CreateError(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.CreateError = errors.New("create error") + config.KubeClient = &failingKubeClient + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + + require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") +} + +func TestInstalLCRDs_AlreadyExist(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + mockError := &apierrors.StatusError{ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Reason: metav1.StatusReasonAlreadyExists, + }} + failingKubeClient.CreateError = mockError + config.KubeClient = &failingKubeClient + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + + assert.Nil(t, instAction.installCRDs(crdsToInstall)) +} + +func TestInstalLCRDs_WaiterError(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.WaitError = errors.New("wait error") + failingKubeClient.BuildDummy = true + config.KubeClient = &failingKubeClient + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + + require.Error(t, instAction.installCRDs(crdsToInstall), "wait error") +} + +func TestCheckDependencies(t *testing.T) { + dependency := chart.Dependency{Name: "hello"} + mockChart := buildChart(withDependency()) + + assert.Nil(t, CheckDependencies(mockChart, []*chart.Dependency{&dependency})) +} + +func TestCheckDependencies_MissingDependency(t *testing.T) { + dependency := chart.Dependency{Name: "missing"} + mockChart := buildChart(withDependency()) + + assert.ErrorContains(t, CheckDependencies(mockChart, []*chart.Dependency{&dependency}), "missing in charts") +} diff --git a/pkg/action/lint_test.go b/pkg/action/lint_test.go index 613149a4d..4684f91f1 100644 --- a/pkg/action/lint_test.go +++ b/pkg/action/lint_test.go @@ -17,7 +17,12 @@ limitations under the License. package action import ( + "errors" "testing" + + "github.com/stretchr/testify/assert" + + "helm.sh/helm/v4/pkg/chart/v2/lint/support" ) var ( @@ -163,3 +168,45 @@ func TestLint_ChartWithWarnings(t *testing.T) { } }) } + +func TestHasWarningsOrErrors(t *testing.T) { + testError := errors.New("test-error") + cases := []struct { + name string + data LintResult + expected bool + }{ + { + name: "has no warning messages and no errors", + data: LintResult{TotalChartsLinted: 1, Messages: make([]support.Message, 0), Errors: make([]error, 0)}, + expected: false, + }, + { + name: "has error", + data: LintResult{TotalChartsLinted: 1, Messages: make([]support.Message, 0), Errors: []error{testError}}, + expected: true, + }, + { + name: "has info message only", + data: LintResult{TotalChartsLinted: 1, Messages: []support.Message{{Severity: support.InfoSev, Path: "", Err: testError}}, Errors: make([]error, 0)}, + expected: false, + }, + { + name: "has warning message", + data: LintResult{TotalChartsLinted: 1, Messages: []support.Message{{Severity: support.WarningSev, Path: "", Err: testError}}, Errors: make([]error, 0)}, + expected: true, + }, + { + name: "has error message", + data: LintResult{TotalChartsLinted: 1, Messages: []support.Message{{Severity: support.ErrorSev, Path: "", Err: testError}}, Errors: make([]error, 0)}, + expected: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + result := HasWarningsOrErrors(&tc.data) + assert.Equal(t, tc.expected, result) + }) + } +} diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go index b0ca0bec4..84dcb71c1 100644 --- a/pkg/action/package_test.go +++ b/pkg/action/package_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/Masterminds/semver/v3" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/internal/test/ensure" ) @@ -90,7 +91,8 @@ func TestPassphraseFileFetcher_WithStdinAndMultipleFetches(t *testing.T) { passphrase := "secret-from-stdin" go func() { - w.Write([]byte(passphrase + "\n")) + _, err = w.Write([]byte(passphrase + "\n")) + require.NoError(t, err) }() for range 4 { @@ -152,3 +154,18 @@ func TestValidateVersion(t *testing.T) { }) } } + +func TestRun_ErrorPath(t *testing.T) { + client := NewPackage() + _, err := client.Run("err-path", nil) + require.Error(t, err) +} + +func TestRun(t *testing.T) { + chartPath := "testdata/charts/chart-with-schema" + client := NewPackage() + filename, err := client.Run(chartPath, nil) + require.NoError(t, err) + require.Equal(t, "empty-0.1.0.tgz", filename) + require.NoError(t, os.Remove(filename)) +} diff --git a/pkg/action/pull_test.go b/pkg/action/pull_test.go new file mode 100644 index 000000000..ba212973e --- /dev/null +++ b/pkg/action/pull_test.go @@ -0,0 +1,80 @@ +/* +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 action + +import ( + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/registry" +) + +func TestNewPull(t *testing.T) { + config := actionConfigFixture(t) + client := NewPull(WithConfig(config)) + + assert.NotNil(t, client) + assert.Equal(t, config, client.cfg) +} + +func TestPullSetRegistryClient(t *testing.T) { + config := actionConfigFixture(t) + client := NewPull(WithConfig(config)) + + registryClient := ®istry.Client{} + client.SetRegistryClient(registryClient) + assert.Equal(t, registryClient, client.cfg.RegistryClient) +} + +func TestPullRun_ChartNotFound(t *testing.T) { + srv, err := startLocalServerForTests(t, nil) + if err != nil { + t.Fatal(err) + } + defer srv.Close() + + config := actionConfigFixture(t) + client := NewPull(WithConfig(config)) + client.Settings = cli.New() + client.RepoURL = srv.URL + + chartRef := "nginx" + _, err = client.Run(chartRef) + require.ErrorContains(t, err, "404 Not Found") +} + +func startLocalServerForTests(t *testing.T, handler http.Handler) (*httptest.Server, error) { + t.Helper() + if handler == nil { + fileBytes, err := os.ReadFile("../repo/v1/testdata/local-index.yaml") + if err != nil { + return nil, err + } + handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, err = w.Write(fileBytes) + require.NoError(t, err) + }) + } + + return httptest.NewServer(handler), nil +} diff --git a/pkg/action/push_test.go b/pkg/action/push_test.go new file mode 100644 index 000000000..3a40cab08 --- /dev/null +++ b/pkg/action/push_test.go @@ -0,0 +1,66 @@ +/* +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 action + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewPushWithPushConfig(t *testing.T) { + config := actionConfigFixture(t) + client := NewPushWithOpts(WithPushConfig(config)) + + assert.NotNil(t, client) + assert.Equal(t, config, client.cfg) +} + +func TestNewPushWithTLSClientConfig(t *testing.T) { + certFile := "certFile" + keyFile := "keyFile" + caFile := "caFile" + client := NewPushWithOpts(WithTLSClientConfig(certFile, keyFile, caFile)) + + assert.NotNil(t, client) + assert.Equal(t, certFile, client.certFile) + assert.Equal(t, keyFile, client.keyFile) + assert.Equal(t, caFile, client.caFile) +} + +func TestNewPushWithInsecureSkipTLSVerify(t *testing.T) { + client := NewPushWithOpts(WithInsecureSkipTLSVerify(true)) + + assert.NotNil(t, client) + assert.Equal(t, true, client.insecureSkipTLSverify) +} + +func TestNewPushWithPlainHTTP(t *testing.T) { + client := NewPushWithOpts(WithPlainHTTP(true)) + + assert.NotNil(t, client) + assert.Equal(t, true, client.plainHTTP) +} + +func TestNewPushWithPushOptWriter(t *testing.T) { + buf := new(bytes.Buffer) + client := NewPushWithOpts(WithPushOptWriter(buf)) + + assert.NotNil(t, client) + assert.Equal(t, buf, client.out) +} diff --git a/pkg/action/registry_login_test.go b/pkg/action/registry_login_test.go new file mode 100644 index 000000000..de2450d9d --- /dev/null +++ b/pkg/action/registry_login_test.go @@ -0,0 +1,84 @@ +/* +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 action + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewRegistryLogin(t *testing.T) { + config := actionConfigFixture(t) + client := NewRegistryLogin(config) + + assert.NotNil(t, client) + assert.Equal(t, config, client.cfg) +} + +func TestWithCertFile(t *testing.T) { + config := actionConfigFixture(t) + client := NewRegistryLogin(config) + + certFile := "testdata/cert.pem" + opt := WithCertFile(certFile) + + assert.Nil(t, opt(client)) + assert.Equal(t, certFile, client.certFile) +} + +func TestWithInsecure(t *testing.T) { + config := actionConfigFixture(t) + client := NewRegistryLogin(config) + + opt := WithInsecure(true) + + assert.Nil(t, opt(client)) + assert.Equal(t, true, client.insecure) +} + +func TestWithKeyFile(t *testing.T) { + config := actionConfigFixture(t) + client := NewRegistryLogin(config) + + keyFile := "testdata/key.pem" + opt := WithKeyFile(keyFile) + + assert.Nil(t, opt(client)) + assert.Equal(t, keyFile, client.keyFile) +} + +func TestWithCAFile(t *testing.T) { + config := actionConfigFixture(t) + client := NewRegistryLogin(config) + + caFile := "testdata/ca.pem" + opt := WithCAFile(caFile) + + assert.Nil(t, opt(client)) + assert.Equal(t, caFile, client.caFile) +} + +func TestWithPlainHTTPLogin(t *testing.T) { + config := actionConfigFixture(t) + client := NewRegistryLogin(config) + + opt := WithPlainHTTPLogin(true) + + assert.Nil(t, opt(client)) + assert.Equal(t, true, client.plainHTTP) +} diff --git a/pkg/action/registry_logout_test.go b/pkg/action/registry_logout_test.go new file mode 100644 index 000000000..669d9c9ba --- /dev/null +++ b/pkg/action/registry_logout_test.go @@ -0,0 +1,31 @@ +/* +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 action + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewRegistryLogout(t *testing.T) { + config := actionConfigFixture(t) + client := NewRegistryLogout(config) + + assert.NotNil(t, client) + assert.Equal(t, config, client.cfg) +} diff --git a/pkg/action/release_testing_test.go b/pkg/action/release_testing_test.go new file mode 100644 index 000000000..a695cb42a --- /dev/null +++ b/pkg/action/release_testing_test.go @@ -0,0 +1,91 @@ +/* +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 action + +import ( + "bytes" + "errors" + "io" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "helm.sh/helm/v4/pkg/cli" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + release "helm.sh/helm/v4/pkg/release/v1" +) + +func TestNewReleaseTesting(t *testing.T) { + config := actionConfigFixture(t) + client := NewReleaseTesting(config) + + assert.NotNil(t, client) + assert.Equal(t, config, client.cfg) +} + +func TestReleaseTestingRun_UnreachableKubeClient(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.ConnectionError = errors.New("connection refused") + config.KubeClient = &failingKubeClient + + client := NewReleaseTesting(config) + result, err := client.Run("") + assert.Nil(t, result) + assert.Error(t, err) +} + +func TestReleaseTestingGetPodLogs_FilterEvents(t *testing.T) { + config := actionConfigFixture(t) + require.NoError(t, config.Init(cli.New().RESTClientGetter(), "", os.Getenv("HELM_DRIVER"))) + client := NewReleaseTesting(config) + client.Filters[ExcludeNameFilter] = []string{"event-1"} + client.Filters[IncludeNameFilter] = []string{"event-3"} + + hooks := []*release.Hook{ + { + Name: "event-1", + Events: []release.HookEvent{release.HookTest}, + }, + { + Name: "event-2", + Events: []release.HookEvent{release.HookTest}, + }, + } + + out := &bytes.Buffer{} + require.NoError(t, client.GetPodLogs(out, &release.Release{Hooks: hooks})) + + assert.Contains(t, "", out.String()) +} + +func TestReleaseTestingGetPodLogs_PodRetrievalError(t *testing.T) { + config := actionConfigFixture(t) + require.NoError(t, config.Init(cli.New().RESTClientGetter(), "", os.Getenv("HELM_DRIVER"))) + client := NewReleaseTesting(config) + + hooks := []*release.Hook{ + { + Name: "event-1", + Events: []release.HookEvent{release.HookTest}, + }, + } + + require.ErrorContains(t, client.GetPodLogs(&bytes.Buffer{}, &release.Release{Hooks: hooks}), "unable to get pod logs") +} diff --git a/pkg/action/rollback_test.go b/pkg/action/rollback_test.go new file mode 100644 index 000000000..5158bee26 --- /dev/null +++ b/pkg/action/rollback_test.go @@ -0,0 +1,45 @@ +/* +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 action + +import ( + "errors" + "io" + "testing" + + "github.com/stretchr/testify/assert" + + kubefake "helm.sh/helm/v4/pkg/kube/fake" +) + +func TestNewRollback(t *testing.T) { + config := actionConfigFixture(t) + client := NewRollback(config) + + assert.NotNil(t, client) + assert.Equal(t, config, client.cfg) +} + +func TestRollbackRun_UnreachableKubeClient(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.ConnectionError = errors.New("connection refused") + config.KubeClient = &failingKubeClient + + client := NewRollback(config) + assert.Error(t, client.Run("")) +} diff --git a/pkg/action/show_test.go b/pkg/action/show_test.go index f3b767fca..6e270ac6d 100644 --- a/pkg/action/show_test.go +++ b/pkg/action/show_test.go @@ -20,8 +20,11 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/registry" ) func TestShow(t *testing.T) { @@ -168,3 +171,12 @@ bar t.Errorf("Expected\n%q\nGot\n%q\n", expect, output) } } + +func TestShowSetRegistryClient(t *testing.T) { + config := actionConfigFixture(t) + client := NewShow(ShowAll, config) + + registryClient := ®istry.Client{} + client.SetRegistryClient(registryClient) + assert.Equal(t, registryClient, client.registryClient) +} diff --git a/pkg/action/status_test.go b/pkg/action/status_test.go new file mode 100644 index 000000000..a031fe334 --- /dev/null +++ b/pkg/action/status_test.go @@ -0,0 +1,140 @@ +/* +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 action + +import ( + "errors" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + kubefake "helm.sh/helm/v4/pkg/kube/fake" + release "helm.sh/helm/v4/pkg/release/v1" +) + +func TestNewStatus(t *testing.T) { + config := actionConfigFixture(t) + client := NewStatus(config) + + assert.NotNil(t, client) + assert.Equal(t, config, client.cfg) + assert.Equal(t, 0, client.Version) +} + +func TestStatusRun(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.BuildDummy = true + config.KubeClient = &failingKubeClient + client := NewStatus(config) + + releaseName := "test-release" + require.NoError(t, configureReleaseContent(config, releaseName)) + result, err := client.Run(releaseName) + client.ShowResourcesTable = true + + require.NoError(t, err) + assert.Equal(t, releaseName, result.Name) + assert.Equal(t, 1, result.Version) +} + +func TestStatusRun_KubeClientNotReachable(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.ConnectionError = errors.New("connection refused") + config.KubeClient = &failingKubeClient + + client := NewStatus(config) + + result, err := client.Run("") + assert.Nil(t, result) + assert.Error(t, err) +} + +func TestStatusRun_KubeClientBuildTableError(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.BuildTableError = errors.New("build table error") + config.KubeClient = &failingKubeClient + + releaseName := "test-release" + require.NoError(t, configureReleaseContent(config, releaseName)) + + client := NewStatus(config) + client.ShowResourcesTable = true + + result, err := client.Run(releaseName) + + assert.Nil(t, result) + assert.ErrorContains(t, err, "build table error") +} + +func TestStatusRun_KubeClientBuildError(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.BuildError = errors.New("build error") + config.KubeClient = &failingKubeClient + + releaseName := "test-release" + require.NoError(t, configureReleaseContent(config, releaseName)) + + client := NewStatus(config) + client.ShowResourcesTable = false + + result, err := client.Run(releaseName) + assert.Nil(t, result) + assert.ErrorContains(t, err, "build error") +} + +func TestStatusRun_KubeClientGetError(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.BuildError = errors.New("get error") + config.KubeClient = &failingKubeClient + + releaseName := "test-release" + require.NoError(t, configureReleaseContent(config, releaseName)) + client := NewStatus(config) + + result, err := client.Run(releaseName) + assert.Nil(t, result) + assert.ErrorContains(t, err, "get error") +} + +func configureReleaseContent(cfg *Configuration, releaseName string) error { + rel := &release.Release{ + Name: releaseName, + Info: &release.Info{ + Status: release.StatusDeployed, + }, + Manifest: testManifest, + Version: 1, + Namespace: "default", + } + + return cfg.Releases.Create(rel) +} + +const testManifest = ` +apiVersion: v1 +kind: Pod +metadata: + namespace: default + name: test-application +` diff --git a/pkg/action/uninstall_test.go b/pkg/action/uninstall_test.go index fba1e391f..6c4ad977f 100644 --- a/pkg/action/uninstall_test.go +++ b/pkg/action/uninstall_test.go @@ -82,7 +82,7 @@ func TestUninstallRelease_deleteRelease(t *testing.T) { "password": "password" } }` - unAction.cfg.Releases.Create(rel) + require.NoError(t, unAction.cfg.Releases.Create(rel)) res, err := unAction.Run(rel.Name) is.NoError(err) expected := `These resources were kept due to the resource policy: @@ -112,7 +112,7 @@ func TestUninstallRelease_Wait(t *testing.T) { "password": "password" } }` - unAction.cfg.Releases.Create(rel) + require.NoError(t, unAction.cfg.Releases.Create(rel)) failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitForDeleteError = fmt.Errorf("U timed out") unAction.cfg.KubeClient = failer @@ -146,7 +146,7 @@ func TestUninstallRelease_Cascade(t *testing.T) { "password": "password" } }` - unAction.cfg.Releases.Create(rel) + require.NoError(t, unAction.cfg.Releases.Create(rel)) failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.DeleteError = fmt.Errorf("Uninstall with cascade failed") failer.BuildDummy = true diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index e1eac3f9f..d75fd518a 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -25,16 +25,19 @@ import ( "testing" "time" - chart "helm.sh/helm/v4/pkg/chart/v2" - "helm.sh/helm/v4/pkg/kube" - "helm.sh/helm/v4/pkg/storage/driver" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/resource" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/kube" kubefake "helm.sh/helm/v4/pkg/kube/fake" "helm.sh/helm/v4/pkg/release/common" + "helm.sh/helm/v4/pkg/registry" release "helm.sh/helm/v4/pkg/release/v1" + "helm.sh/helm/v4/pkg/storage/driver" ) func upgradeAction(t *testing.T) *Upgrade { @@ -85,7 +88,7 @@ func TestUpgradeRelease_Wait(t *testing.T) { rel := releaseStub() rel.Name = "come-fail-away" rel.Info.Status = common.StatusDeployed - upAction.cfg.Releases.Create(rel) + require.NoError(t, upAction.cfg.Releases.Create(rel)) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") @@ -109,7 +112,7 @@ func TestUpgradeRelease_WaitForJobs(t *testing.T) { rel := releaseStub() rel.Name = "come-fail-away" rel.Info.Status = common.StatusDeployed - upAction.cfg.Releases.Create(rel) + require.NoError(t, upAction.cfg.Releases.Create(rel)) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") @@ -134,7 +137,7 @@ func TestUpgradeRelease_CleanupOnFail(t *testing.T) { rel := releaseStub() rel.Name = "come-fail-away" rel.Info.Status = common.StatusDeployed - upAction.cfg.Releases.Create(rel) + require.NoError(t, upAction.cfg.Releases.Create(rel)) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitError = fmt.Errorf("I timed out") @@ -163,7 +166,7 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) { rel := releaseStub() rel.Name = "nuketown" rel.Info.Status = common.StatusDeployed - upAction.cfg.Releases.Create(rel) + require.NoError(t, upAction.cfg.Releases.Create(rel)) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) // We can't make Update error because then the rollback won't work @@ -193,7 +196,7 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) { rel := releaseStub() rel.Name = "fallout" rel.Info.Status = common.StatusDeployed - upAction.cfg.Releases.Create(rel) + require.NoError(t, upAction.cfg.Releases.Create(rel)) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.UpdateError = fmt.Errorf("update fail") @@ -401,12 +404,12 @@ func TestUpgradeRelease_Pending(t *testing.T) { rel := releaseStub() rel.Name = "come-fail-away" rel.Info.Status = common.StatusDeployed - upAction.cfg.Releases.Create(rel) + require.NoError(t, upAction.cfg.Releases.Create(rel)) rel2 := releaseStub() rel2.Name = "come-fail-away" rel2.Info.Status = common.StatusPendingUpgrade rel2.Version = 2 - upAction.cfg.Releases.Create(rel2) + require.NoError(t, upAction.cfg.Releases.Create(rel2)) vals := map[string]interface{}{} @@ -422,7 +425,7 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { rel := releaseStub() rel.Name = "interrupted-release" rel.Info.Status = common.StatusDeployed - upAction.cfg.Releases.Create(rel) + require.NoError(t, upAction.cfg.Releases.Create(rel)) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitDuration = 10 * time.Second @@ -451,7 +454,7 @@ func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) { rel := releaseStub() rel.Name = "interrupted-release" rel.Info.Status = common.StatusDeployed - upAction.cfg.Releases.Create(rel) + require.NoError(t, upAction.cfg.Releases.Create(rel)) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitDuration = 5 * time.Second @@ -755,3 +758,20 @@ func TestUpgradeRun_UnreachableKubeClient(t *testing.T) { assert.Nil(t, result) assert.ErrorContains(t, err, "connection refused") } + +func TestUpgradeSetRegistryClient(t *testing.T) { + config := actionConfigFixture(t) + client := NewUpgrade(config) + + registryClient := ®istry.Client{} + client.SetRegistryClient(registryClient) + assert.Equal(t, registryClient, client.registryClient) +} + +func TestObjectKey(t *testing.T) { + obj := &appsv1.Deployment{} + obj.SetGroupVersionKind(schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}) + info := resource.Info{Name: "name", Namespace: "namespace", Object: obj} + + assert.Equal(t, "apps/v1/Deployment/namespace/name", objectKey(&info)) +} diff --git a/pkg/action/verify_test.go b/pkg/action/verify_test.go new file mode 100644 index 000000000..1e11ff4c5 --- /dev/null +++ b/pkg/action/verify_test.go @@ -0,0 +1,42 @@ +/* +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 action + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewVerify(t *testing.T) { + client := NewVerify() + + assert.NotNil(t, client) +} + +func TestVerifyRun(t *testing.T) { + client := NewVerify() + + client.Keyring = "../downloader/testdata/helm-test-key.pub" + require.NoError(t, client.Run("../downloader/testdata/signtest-0.1.0.tgz")) +} + +func TestVerifyRun_DownloadError(t *testing.T) { + client := NewVerify() + require.Error(t, client.Run("invalid-chart-path")) +} diff --git a/pkg/kube/fake/failing_kube_client.go b/pkg/kube/fake/failing_kube_client.go index f340c045f..31d0082cc 100644 --- a/pkg/kube/fake/failing_kube_client.go +++ b/pkg/kube/fake/failing_kube_client.go @@ -146,6 +146,9 @@ func (f *FailingKubeClient) BuildTable(r io.Reader, _ bool) (kube.ResourceList, if f.BuildTableError != nil { return []*resource.Info{}, f.BuildTableError } + if f.BuildDummy { + return createDummyResourceList(), nil + } return f.PrintingKubeClient.BuildTable(r, false) } From 8cf4ad73c3d6d8296b98286a9232155b7ecbd45b Mon Sep 17 00:00:00 2001 From: Stephanie Hohenberg Date: Tue, 21 Oct 2025 19:37:07 +0100 Subject: [PATCH 065/571] updates tests after rebase from master Signed-off-by: Stephanie Hohenberg --- pkg/action/get_test.go | 11 +++++++---- pkg/action/history.go | 2 +- pkg/action/history_test.go | 23 +++++++++++++++-------- pkg/action/install.go | 1 - pkg/action/install_test.go | 18 ++++-------------- pkg/action/release_testing_test.go | 2 +- pkg/action/status_test.go | 9 ++++++--- pkg/action/upgrade_test.go | 2 +- pkg/action/verify_test.go | 10 ++++++++-- 9 files changed, 43 insertions(+), 35 deletions(-) diff --git a/pkg/action/get_test.go b/pkg/action/get_test.go index 53a0645eb..876819ee4 100644 --- a/pkg/action/get_test.go +++ b/pkg/action/get_test.go @@ -25,7 +25,8 @@ import ( "github.com/stretchr/testify/require" kubefake "helm.sh/helm/v4/pkg/kube/fake" - release "helm.sh/helm/v4/pkg/release/v1" + + "helm.sh/helm/v4/pkg/release/common" ) func TestNewGet(t *testing.T) { @@ -40,11 +41,13 @@ func TestNewGet(t *testing.T) { func TestGetRun(t *testing.T) { config := actionConfigFixture(t) client := NewGet(config) - simpleRelease := namedReleaseStub("test-release", release.StatusPendingUpgrade) + simpleRelease := namedReleaseStub("test-release", common.StatusPendingUpgrade) require.NoError(t, config.Releases.Create(simpleRelease)) - result, err := client.Run(simpleRelease.Name) + releaser, err := client.Run(simpleRelease.Name) + require.NoError(t, err) + result, err := releaserToV1Release(releaser) require.NoError(t, err) assert.Equal(t, simpleRelease.Name, result.Name) assert.Equal(t, simpleRelease.Version, result.Version) @@ -57,7 +60,7 @@ func TestGetRun_UnreachableKubeClient(t *testing.T) { config.KubeClient = &failingKubeClient client := NewGet(config) - simpleRelease := namedReleaseStub("test-release", release.StatusPendingUpgrade) + simpleRelease := namedReleaseStub("test-release", common.StatusPendingUpgrade) require.NoError(t, config.Releases.Create(simpleRelease)) result, err := client.Run(simpleRelease.Name) diff --git a/pkg/action/history.go b/pkg/action/history.go index 90307c79b..3d561b3ad 100644 --- a/pkg/action/history.go +++ b/pkg/action/history.go @@ -20,7 +20,7 @@ import ( "fmt" chartutil "helm.sh/helm/v4/pkg/chart/v2/util" - release "helm.sh/helm/v4/pkg/release" + "helm.sh/helm/v4/pkg/release" ) // History is the action for checking the release's ledger. diff --git a/pkg/action/history_test.go b/pkg/action/history_test.go index 31c2c87d0..31fdd4a96 100644 --- a/pkg/action/history_test.go +++ b/pkg/action/history_test.go @@ -26,6 +26,8 @@ import ( kubefake "helm.sh/helm/v4/pkg/kube/fake" release "helm.sh/helm/v4/pkg/release/v1" + + "helm.sh/helm/v4/pkg/release/common" ) func TestNewHistory(t *testing.T) { @@ -38,8 +40,8 @@ func TestNewHistory(t *testing.T) { func TestHistoryRun(t *testing.T) { releaseName := "test-release" - simpleRelease := namedReleaseStub(releaseName, release.StatusPendingUpgrade) - updatedRelease := namedReleaseStub(releaseName, release.StatusDeployed) + simpleRelease := namedReleaseStub(releaseName, common.StatusPendingUpgrade) + updatedRelease := namedReleaseStub(releaseName, common.StatusDeployed) updatedRelease.Chart.Metadata.Version = "0.1.1" updatedRelease.Version = 2 @@ -57,14 +59,19 @@ func TestHistoryRun(t *testing.T) { require.NoError(t, err) assert.Len(t, releases, 2, "expected 2 Releases in Config") - result, err := client.Run(releaseName) + releasers, err := client.Run(releaseName) + require.NoError(t, err) + assert.Len(t, releasers, 2, "expected 2 Releases in History result") + + release1, err := releaserToV1Release(releasers[0]) require.NoError(t, err) + assert.Equal(t, simpleRelease.Name, release1.Name) + assert.Equal(t, simpleRelease.Version, release1.Version) - assert.Len(t, result, 2, "expected 2 Releases in History result") - assert.Equal(t, simpleRelease.Name, result[0].Name) - assert.Equal(t, simpleRelease.Version, result[0].Version) - assert.Equal(t, updatedRelease.Name, result[1].Name) - assert.Equal(t, updatedRelease.Version, result[1].Version) + release2, err := releaserToV1Release(releasers[1]) + require.NoError(t, err) + assert.Equal(t, updatedRelease.Name, release2.Name) + assert.Equal(t, updatedRelease.Version, release2.Version) } func TestHistoryRun_UnreachableKubeClient(t *testing.T) { diff --git a/pkg/action/install.go b/pkg/action/install.go index 3ec11e52f..8a0df6346 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -116,7 +116,6 @@ type Install struct { Labels map[string]string // KubeVersion allows specifying a custom kubernetes version to use and // APIVersions allows a manual set of supported API Versions to be passed - // (for things like templating). These are ignored if ClientOnly is false KubeVersion *common.KubeVersion APIVersions common.VersionSet // Used by helm template to render charts with .Release.IsUpgrade. Ignored if Dry-Run is false diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 41d56152e..84b94b431 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -34,6 +34,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + ci "helm.sh/helm/v4/pkg/chart" appsv1 "k8s.io/api/apps/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -49,8 +50,8 @@ import ( chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/kube" kubefake "helm.sh/helm/v4/pkg/kube/fake" - rcommon "helm.sh/helm/v4/pkg/release/common" "helm.sh/helm/v4/pkg/registry" + rcommon "helm.sh/helm/v4/pkg/release/common" release "helm.sh/helm/v4/pkg/release/v1" "helm.sh/helm/v4/pkg/storage/driver" ) @@ -275,17 +276,6 @@ func TestInstallReleaseWithValues(t *testing.T) { is.Equal(expectedUserValues, rel.Config) } -func TestInstallReleaseClientOnly(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.ClientOnly = true - _, err := instAction.Run(buildChart(), nil) - require.NoError(t, err) - - is.Equal(instAction.cfg.Capabilities, common.DefaultCapabilities) - is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: io.Discard}) -} - func TestInstallRelease_NoName(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "" @@ -1186,12 +1176,12 @@ func TestCheckDependencies(t *testing.T) { dependency := chart.Dependency{Name: "hello"} mockChart := buildChart(withDependency()) - assert.Nil(t, CheckDependencies(mockChart, []*chart.Dependency{&dependency})) + assert.Nil(t, CheckDependencies(mockChart, []ci.Dependency{&dependency})) } func TestCheckDependencies_MissingDependency(t *testing.T) { dependency := chart.Dependency{Name: "missing"} mockChart := buildChart(withDependency()) - assert.ErrorContains(t, CheckDependencies(mockChart, []*chart.Dependency{&dependency}), "missing in charts") + assert.ErrorContains(t, CheckDependencies(mockChart, []ci.Dependency{&dependency}), "missing in charts") } diff --git a/pkg/action/release_testing_test.go b/pkg/action/release_testing_test.go index a695cb42a..1ada78a4b 100644 --- a/pkg/action/release_testing_test.go +++ b/pkg/action/release_testing_test.go @@ -72,7 +72,7 @@ func TestReleaseTestingGetPodLogs_FilterEvents(t *testing.T) { out := &bytes.Buffer{} require.NoError(t, client.GetPodLogs(out, &release.Release{Hooks: hooks})) - assert.Contains(t, "", out.String()) + assert.Empty(t, out.String()) } func TestReleaseTestingGetPodLogs_PodRetrievalError(t *testing.T) { diff --git a/pkg/action/status_test.go b/pkg/action/status_test.go index a031fe334..674715aff 100644 --- a/pkg/action/status_test.go +++ b/pkg/action/status_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/require" kubefake "helm.sh/helm/v4/pkg/kube/fake" + rcommon "helm.sh/helm/v4/pkg/release/common" release "helm.sh/helm/v4/pkg/release/v1" ) @@ -43,12 +44,14 @@ func TestStatusRun(t *testing.T) { failingKubeClient.BuildDummy = true config.KubeClient = &failingKubeClient client := NewStatus(config) + client.ShowResourcesTable = true releaseName := "test-release" require.NoError(t, configureReleaseContent(config, releaseName)) - result, err := client.Run(releaseName) - client.ShowResourcesTable = true + releaser, err := client.Run(releaseName) + require.NoError(t, err) + result, err := releaserToV1Release(releaser) require.NoError(t, err) assert.Equal(t, releaseName, result.Name) assert.Equal(t, 1, result.Version) @@ -121,7 +124,7 @@ func configureReleaseContent(cfg *Configuration, releaseName string) error { rel := &release.Release{ Name: releaseName, Info: &release.Info{ - Status: release.StatusDeployed, + Status: rcommon.StatusDeployed, }, Manifest: testManifest, Version: 1, diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index d75fd518a..17c4927cc 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -34,8 +34,8 @@ import ( chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/kube" kubefake "helm.sh/helm/v4/pkg/kube/fake" - "helm.sh/helm/v4/pkg/release/common" "helm.sh/helm/v4/pkg/registry" + "helm.sh/helm/v4/pkg/release/common" release "helm.sh/helm/v4/pkg/release/v1" "helm.sh/helm/v4/pkg/storage/driver" ) diff --git a/pkg/action/verify_test.go b/pkg/action/verify_test.go index 1e11ff4c5..343dacaef 100644 --- a/pkg/action/verify_test.go +++ b/pkg/action/verify_test.go @@ -33,10 +33,16 @@ func TestVerifyRun(t *testing.T) { client := NewVerify() client.Keyring = "../downloader/testdata/helm-test-key.pub" - require.NoError(t, client.Run("../downloader/testdata/signtest-0.1.0.tgz")) + output, err := client.Run("../downloader/testdata/signtest-0.1.0.tgz") + assert.Contains(t, output, "Signed by:") + assert.Contains(t, output, "Using Key With Fingerprint:") + assert.Contains(t, output, "Chart Hash Verified:") + require.NoError(t, err) } func TestVerifyRun_DownloadError(t *testing.T) { client := NewVerify() - require.Error(t, client.Run("invalid-chart-path")) + output, err := client.Run("invalid-chart-path") + require.Error(t, err) + assert.Empty(t, output) } From bb9356e182a8abb0f806cd4866a317f273631862 Mon Sep 17 00:00:00 2001 From: Stephanie Hohenberg Date: Tue, 21 Oct 2025 19:42:37 +0100 Subject: [PATCH 066/571] fixes lint issue Signed-off-by: Stephanie Hohenberg --- pkg/action/install_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 84b94b431..dd305b4f4 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -34,7 +34,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - ci "helm.sh/helm/v4/pkg/chart" appsv1 "k8s.io/api/apps/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -45,6 +44,8 @@ import ( "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest/fake" + ci "helm.sh/helm/v4/pkg/chart" + "helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" From 2f598ffc850d9f83642da362a7be4121cf617275 Mon Sep 17 00:00:00 2001 From: Stephanie Hohenberg Date: Wed, 3 Dec 2025 13:21:25 +0530 Subject: [PATCH 067/571] fixes tests after merge Signed-off-by: Stephanie Hohenberg --- pkg/action/push_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/action/push_test.go b/pkg/action/push_test.go index 3a40cab08..35c6f3efc 100644 --- a/pkg/action/push_test.go +++ b/pkg/action/push_test.go @@ -47,7 +47,7 @@ func TestNewPushWithInsecureSkipTLSVerify(t *testing.T) { client := NewPushWithOpts(WithInsecureSkipTLSVerify(true)) assert.NotNil(t, client) - assert.Equal(t, true, client.insecureSkipTLSverify) + assert.Equal(t, true, client.insecureSkipTLSVerify) } func TestNewPushWithPlainHTTP(t *testing.T) { From e62bf7fcc9ebea86c1510be9a284b99d7438cf7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 21:08:04 +0000 Subject: [PATCH 068/571] chore(deps): bump golangci/golangci-lint-action from 9.1.0 to 9.2.0 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 9.1.0 to 9.2.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/e7fa5ac41e1cf5b7d48e45e42232ce7ada589601...1e7e51e771db61008b38414a730f564565cf7c20) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-version: 9.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/golangci-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index b9d376519..2fec20c60 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -22,6 +22,6 @@ jobs: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true - name: golangci-lint - uses: golangci/golangci-lint-action@e7fa5ac41e1cf5b7d48e45e42232ce7ada589601 #pin@9.1.0 + uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 #pin@9.2.0 with: version: ${{ env.GOLANGCI_LINT_VERSION }} From 581ab1afe9d8a7e11f78154f86abbeb7adf2ff50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 21:08:08 +0000 Subject: [PATCH 069/571] chore(deps): bump actions/stale from 10.1.0 to 10.1.1 Bumps [actions/stale](https://github.com/actions/stale) from 10.1.0 to 10.1.1. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/5f858e3efba33a5ca4407a664cc011ad407f2008...997185467fa4f803885201cee163a9f38240193d) --- updated-dependencies: - dependency-name: actions/stale dependency-version: 10.1.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/stale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 3d72d1e17..574427a5d 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 + - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.' From a109ac2f06d7cdca85c23567f3c545c726727904 Mon Sep 17 00:00:00 2001 From: Stephanie Hohenberg Date: Thu, 4 Dec 2025 17:47:54 +0530 Subject: [PATCH 070/571] fixes comment in install.go Signed-off-by: Stephanie Hohenberg --- pkg/action/install.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/action/install.go b/pkg/action/install.go index 8a0df6346..990581807 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -116,6 +116,7 @@ type Install struct { Labels map[string]string // KubeVersion allows specifying a custom kubernetes version to use and // APIVersions allows a manual set of supported API Versions to be passed + // (for things like templating). KubeVersion *common.KubeVersion APIVersions common.VersionSet // Used by helm template to render charts with .Release.IsUpgrade. Ignored if Dry-Run is false From 119341dca7fba0ae2987b4d0ffd41e9574e03a38 Mon Sep 17 00:00:00 2001 From: Zadkiel AHARONIAN Date: Sat, 6 Dec 2025 18:39:19 +0100 Subject: [PATCH 071/571] fix(getter): pass settings environment variables Signed-off-by: Zadkiel AHARONIAN --- internal/plugin/runtime.go | 8 ++++--- internal/plugin/runtime_extismv1.go | 2 +- internal/plugin/runtime_subprocess.go | 16 ++++++------- internal/plugin/runtime_subprocess_getter.go | 6 ++--- internal/plugin/runtime_test.go | 4 ++-- pkg/getter/plugingetter.go | 4 ++++ pkg/getter/plugingetter_test.go | 24 ++++++++++++++++++++ 7 files changed, 47 insertions(+), 17 deletions(-) diff --git a/internal/plugin/runtime.go b/internal/plugin/runtime.go index b2ff0b7ca..7d39a9a43 100644 --- a/internal/plugin/runtime.go +++ b/internal/plugin/runtime.go @@ -53,13 +53,13 @@ func remarshalRuntimeConfig[T RuntimeConfig](runtimeData map[string]any) (Runtim return config, nil } -// parseEnv takes a list of "KEY=value" environment variable strings +// ParseEnv takes a list of "KEY=value" environment variable strings // and transforms the result into a map[KEY]=value // // - empty input strings are ignored // - input strings with no value are stored as empty strings // - duplicate keys overwrite earlier values -func parseEnv(env []string) map[string]string { +func ParseEnv(env []string) map[string]string { result := make(map[string]string, len(env)) for _, envVar := range env { parts := strings.SplitN(envVar, "=", 2) @@ -75,7 +75,9 @@ func parseEnv(env []string) map[string]string { return result } -func formatEnv(env map[string]string) []string { +// FormatEnv takes a map[KEY]=value and transforms it into +// a list of "KEY=value" environment variable strings +func FormatEnv(env map[string]string) []string { result := make([]string, 0, len(env)) for key, value := range env { result = append(result, fmt.Sprintf("%s=%s", key, value)) diff --git a/internal/plugin/runtime_extismv1.go b/internal/plugin/runtime_extismv1.go index b5cc79a6f..cd9a02535 100644 --- a/internal/plugin/runtime_extismv1.go +++ b/internal/plugin/runtime_extismv1.go @@ -259,7 +259,7 @@ func buildPluginConfig(input *Input, r *RuntimeExtismV1) extism.PluginConfig { mc = mc.WithStderr(input.Stderr) } if len(input.Env) > 0 { - env := parseEnv(input.Env) + env := ParseEnv(input.Env) for k, v := range env { mc = mc.WithEnv(k, v) } diff --git a/internal/plugin/runtime_subprocess.go b/internal/plugin/runtime_subprocess.go index 802732b14..c836c1c6d 100644 --- a/internal/plugin/runtime_subprocess.go +++ b/internal/plugin/runtime_subprocess.go @@ -139,7 +139,7 @@ func (r *SubprocessPluginRuntime) InvokeHook(event string) error { return nil } - env := parseEnv(os.Environ()) + env := ParseEnv(os.Environ()) maps.Insert(env, maps.All(r.EnvVars)) env["HELM_PLUGIN_NAME"] = r.metadata.Name env["HELM_PLUGIN_DIR"] = r.pluginDir @@ -150,7 +150,7 @@ func (r *SubprocessPluginRuntime) InvokeHook(event string) error { } cmd := exec.Command(main, argv...) - cmd.Env = formatEnv(env) + cmd.Env = FormatEnv(env) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -198,9 +198,9 @@ func (r *SubprocessPluginRuntime) runCLI(input *Input) (*Output, error) { cmds := r.RuntimeConfig.PlatformCommand - env := parseEnv(os.Environ()) + env := ParseEnv(os.Environ()) maps.Insert(env, maps.All(r.EnvVars)) - maps.Insert(env, maps.All(parseEnv(input.Env))) + maps.Insert(env, maps.All(ParseEnv(input.Env))) env["HELM_PLUGIN_NAME"] = r.metadata.Name env["HELM_PLUGIN_DIR"] = r.pluginDir @@ -210,7 +210,7 @@ func (r *SubprocessPluginRuntime) runCLI(input *Input) (*Output, error) { } cmd := exec.Command(command, args...) - cmd.Env = formatEnv(env) + cmd.Env = FormatEnv(env) cmd.Stdin = input.Stdin cmd.Stdout = input.Stdout @@ -231,9 +231,9 @@ func (r *SubprocessPluginRuntime) runPostrenderer(input *Input) (*Output, error) return nil, fmt.Errorf("plugin %q input message does not implement InputMessagePostRendererV1", r.metadata.Name) } - env := parseEnv(os.Environ()) + env := ParseEnv(os.Environ()) maps.Insert(env, maps.All(r.EnvVars)) - maps.Insert(env, maps.All(parseEnv(input.Env))) + maps.Insert(env, maps.All(ParseEnv(input.Env))) env["HELM_PLUGIN_NAME"] = r.metadata.Name env["HELM_PLUGIN_DIR"] = r.pluginDir @@ -261,7 +261,7 @@ func (r *SubprocessPluginRuntime) runPostrenderer(input *Input) (*Output, error) postRendered := &bytes.Buffer{} stderr := &bytes.Buffer{} - cmd.Env = formatEnv(env) + cmd.Env = FormatEnv(env) cmd.Stdout = postRendered cmd.Stderr = stderr diff --git a/internal/plugin/runtime_subprocess_getter.go b/internal/plugin/runtime_subprocess_getter.go index 6a41b149f..fa6f470a9 100644 --- a/internal/plugin/runtime_subprocess_getter.go +++ b/internal/plugin/runtime_subprocess_getter.go @@ -56,9 +56,9 @@ func (r *SubprocessPluginRuntime) runGetter(input *Input) (*Output, error) { return nil, fmt.Errorf("no downloader found for protocol %q", msg.Protocol) } - env := parseEnv(os.Environ()) + env := ParseEnv(os.Environ()) maps.Insert(env, maps.All(r.EnvVars)) - maps.Insert(env, maps.All(parseEnv(input.Env))) + maps.Insert(env, maps.All(ParseEnv(input.Env))) env["HELM_PLUGIN_NAME"] = r.metadata.Name env["HELM_PLUGIN_DIR"] = r.pluginDir env["HELM_PLUGIN_USERNAME"] = msg.Options.Username @@ -83,7 +83,7 @@ func (r *SubprocessPluginRuntime) runGetter(input *Input) (*Output, error) { cmd := exec.Command( pluginCommand, args...) - cmd.Env = formatEnv(env) + cmd.Env = FormatEnv(env) cmd.Stdout = &buf cmd.Stderr = os.Stderr diff --git a/internal/plugin/runtime_test.go b/internal/plugin/runtime_test.go index f8fe481c1..5552af08e 100644 --- a/internal/plugin/runtime_test.go +++ b/internal/plugin/runtime_test.go @@ -56,7 +56,7 @@ func TestParseEnv(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { - result := parseEnv(tc.env) + result := ParseEnv(tc.env) assert.Equal(t, tc.expected, result) }) } @@ -93,7 +93,7 @@ func TestFormatEnv(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { - result := formatEnv(tc.env) + result := FormatEnv(tc.env) assert.ElementsMatch(t, tc.expected, result) }) } diff --git a/pkg/getter/plugingetter.go b/pkg/getter/plugingetter.go index d74611637..ef8b87503 100644 --- a/pkg/getter/plugingetter.go +++ b/pkg/getter/plugingetter.go @@ -38,12 +38,14 @@ func collectGetterPlugins(settings *cli.EnvSettings) (Providers, error) { if err != nil { return nil, err } + env := plugin.FormatEnv(settings.EnvVars()) pluginConstructorBuilder := func(plg plugin.Plugin) Constructor { return func(option ...Option) (Getter, error) { return &getterPlugin{ options: append([]Option{}, option...), plg: plg, + env: env, }, nil } } @@ -91,6 +93,7 @@ func convertOptions(globalOptions, options []Option) schema.GetterOptionsV1 { type getterPlugin struct { options []Option plg plugin.Plugin + env []string } func (g *getterPlugin) Get(href string, options ...Option) (*bytes.Buffer, error) { @@ -108,6 +111,7 @@ func (g *getterPlugin) Get(href string, options ...Option) (*bytes.Buffer, error Options: opts, Protocol: u.Scheme, }, + Env: g.env, // TODO should we pass Stdin, Stdout, and Stderr through Input here to getter plugins? // Stdout: os.Stdout, } diff --git a/pkg/getter/plugingetter_test.go b/pkg/getter/plugingetter_test.go index 8faaf7329..16af9eb31 100644 --- a/pkg/getter/plugingetter_test.go +++ b/pkg/getter/plugingetter_test.go @@ -144,3 +144,27 @@ func TestGetterPlugin(t *testing.T) { assert.Equal(t, "fake-plugin output", buf.String()) } + +func TestCollectGetterPluginsPassesEnv(t *testing.T) { + env := cli.New() + env.PluginsDirectory = pluginDir + env.Debug = true + + providers, err := collectGetterPlugins(env) + require.NoError(t, err) + require.NotEmpty(t, providers, "expected at least one plugin provider") + + getter, err := providers.ByScheme("test") + require.NoError(t, err) + + gp, ok := getter.(*getterPlugin) + require.True(t, ok, "expected getter to be a *getterPlugin") + + require.NotEmpty(t, gp.env, "expected env to be set on getterPlugin") + envMap := plugin.ParseEnv(gp.env) + + assert.Contains(t, envMap, "HELM_DEBUG", "expected HELM_DEBUG in env") + assert.Equal(t, "true", envMap["HELM_DEBUG"], "expected HELM_DEBUG to be true") + assert.Contains(t, envMap, "HELM_PLUGINS", "expected HELM_PLUGINS in env") + assert.Equal(t, pluginDir, envMap["HELM_PLUGINS"], "expected HELM_PLUGINS to match pluginsDirectory") +} From 8534663e730f7d32ac8777c3b41a7e6c6a94ab96 Mon Sep 17 00:00:00 2001 From: Zadkiel AHARONIAN Date: Sun, 7 Dec 2025 17:09:13 +0100 Subject: [PATCH 072/571] fix(cli): handle nil config in EnvSettings.Namespace() Signed-off-by: Zadkiel AHARONIAN --- pkg/cli/environment.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 106d24336..c53ba633b 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -274,8 +274,10 @@ func (s *EnvSettings) EnvVars() map[string]string { // Namespace gets the namespace from the configuration func (s *EnvSettings) Namespace() string { - if ns, _, err := s.config.ToRawKubeConfigLoader().Namespace(); err == nil { - return ns + if s.config != nil { + if ns, _, err := s.config.ToRawKubeConfigLoader().Namespace(); err == nil { + return ns + } } if s.namespace != "" { return s.namespace From a18e59e46516bfe07241bf2e89de6268660a13ae Mon Sep 17 00:00:00 2001 From: Mads Jensen Date: Mon, 8 Dec 2025 19:52:01 +0100 Subject: [PATCH 073/571] Enable the sloglint linter Signed-off-by: Mads Jensen --- .golangci.yml | 1 + internal/chart/v3/util/dependencies.go | 7 +++- internal/plugin/installer/installer.go | 10 ++++-- pkg/action/action.go | 7 +++- pkg/action/upgrade.go | 6 +++- pkg/chart/v2/util/dependencies.go | 6 +++- pkg/cmd/search_repo.go | 2 +- pkg/engine/lookup_func.go | 12 +++++-- pkg/ignore/rules.go | 10 ++++-- pkg/kube/client.go | 46 ++++++++++++++++++++++---- pkg/kube/wait.go | 17 +++++++--- pkg/storage/driver/cfgmaps.go | 14 +++++--- pkg/storage/driver/secrets.go | 11 ++++-- pkg/storage/driver/sql.go | 45 +++++++++++++++++-------- pkg/storage/storage.go | 2 +- 15 files changed, 152 insertions(+), 44 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 236dadf7b..baa3d96e7 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -26,6 +26,7 @@ linters: - misspell - nakedret - revive + - sloglint - staticcheck - thelper - unused diff --git a/internal/chart/v3/util/dependencies.go b/internal/chart/v3/util/dependencies.go index cd7a8b78c..4ef9e6961 100644 --- a/internal/chart/v3/util/dependencies.go +++ b/internal/chart/v3/util/dependencies.go @@ -280,7 +280,12 @@ 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, slog.Any("error", err)) + slog.Warn( + "ImportValues missing table from chart", + slog.String("chart", "chart"), + slog.String("name", r.Name), + slog.Any("error", err), + ) continue } // create value map from child to be merged into parent diff --git a/internal/plugin/installer/installer.go b/internal/plugin/installer/installer.go index c7c1a8801..271e64ffc 100644 --- a/internal/plugin/installer/installer.go +++ b/internal/plugin/installer/installer.go @@ -80,7 +80,7 @@ func InstallWithOptions(i Installer, opts Options) (*VerificationResult, error) return nil, err } if _, pathErr := os.Stat(i.Path()); !os.IsNotExist(pathErr) { - slog.Warn("plugin already exists", "path", i.Path(), slog.Any("error", pathErr)) + slog.Warn("plugin already exists", slog.String("path", i.Path()), slog.Any("error", pathErr)) return nil, errors.New("plugin already exists") } @@ -132,7 +132,7 @@ func InstallWithOptions(i Installer, opts Options) (*VerificationResult, error) // Update updates a plugin. func Update(i Installer) error { if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) { - slog.Warn("plugin does not exist", "path", i.Path(), slog.Any("error", pathErr)) + slog.Warn("plugin does not exist", slog.String("path", i.Path()), slog.Any("error", pathErr)) return errors.New("plugin does not exist") } return i.Update() @@ -163,7 +163,11 @@ func NewForSource(source, version string) (installer Installer, err error) { func FindSource(location string) (Installer, error) { installer, err := existingVCSRepo(location) if err != nil && err.Error() == "Cannot detect VCS" { - slog.Warn("cannot get information about plugin source", "location", location, slog.Any("error", err)) + slog.Warn( + "cannot get information about plugin source", + slog.String("location", location), + slog.Any("error", err), + ) return installer, errors.New("cannot get information about plugin source") } return installer, err diff --git a/pkg/action/action.go b/pkg/action/action.go index 9555006be..e923240f9 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -502,7 +502,12 @@ func GetVersionSet(client discovery.ServerResourcesInterface) (common.VersionSet // recordRelease with an update operation in case reuse has been set. func (cfg *Configuration) recordRelease(r *release.Release) { if err := cfg.Releases.Update(r); err != nil { - cfg.Logger().Warn("failed to update release", "name", r.Name, "revision", r.Version, slog.Any("error", err)) + cfg.Logger().Warn( + "failed to update release", + slog.String("name", r.Name), + slog.Int("revision", r.Version), + slog.Any("error", err), + ) } } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 57a4a0272..13d28fd4d 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -515,7 +515,11 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, err error) (*release.Release, error) { msg := fmt.Sprintf("Upgrade %q failed: %s", rel.Name, err) - u.cfg.Logger().Warn("upgrade failed", "name", rel.Name, slog.Any("error", err)) + u.cfg.Logger().Warn( + "upgrade failed", + slog.String("name", rel.Name), + slog.Any("error", err), + ) rel.Info.Status = rcommon.StatusFailed rel.Info.Description = msg diff --git a/pkg/chart/v2/util/dependencies.go b/pkg/chart/v2/util/dependencies.go index 294b782f8..c7bb6621e 100644 --- a/pkg/chart/v2/util/dependencies.go +++ b/pkg/chart/v2/util/dependencies.go @@ -280,7 +280,11 @@ 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, slog.Any("error", err)) + slog.Warn( + "ImportValues missing table from chart", + slog.String("chart", r.Name), + slog.Any("error", err), + ) continue } // create value map from child to be merged into parent diff --git a/pkg/cmd/search_repo.go b/pkg/cmd/search_repo.go index 07345a48f..d54e85d91 100644 --- a/pkg/cmd/search_repo.go +++ b/pkg/cmd/search_repo.go @@ -190,7 +190,7 @@ func (o *searchRepoOptions) buildIndex() (*search.Index, error) { f := filepath.Join(o.repoCacheDir, helmpath.CacheIndexFile(n)) ind, err := repo.LoadIndexFile(f) if err != nil { - slog.Warn("repo is corrupt or missing", "repo", n, slog.Any("error", err)) + slog.Warn("repo is corrupt or missing", slog.String("repo", n), slog.Any("error", err)) continue } diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go index 18ed2b63b..c6ad8d252 100644 --- a/pkg/engine/lookup_func.go +++ b/pkg/engine/lookup_func.go @@ -98,7 +98,11 @@ 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(), slog.Any("error", err)) + slog.Error( + "unable to get apiresource", + slog.String("groupVersionKind", gvk.String()), + slog.Any("error", err), + ) return nil, false, fmt.Errorf("unable to get apiresource from unstructured: %s: %w", gvk.String(), err) } gvr := schema.GroupVersionResource{ @@ -124,7 +128,11 @@ func getAPIResourceForGVK(gvk schema.GroupVersionKind, config *rest.Config) (met } resList, err := discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String()) if err != nil { - slog.Error("unable to retrieve resource list", "GroupVersion", gvk.GroupVersion().String(), slog.Any("error", err)) + slog.Error( + "unable to retrieve resource list", + slog.String("GroupVersion", gvk.GroupVersion().String()), + slog.Any("error", err), + ) return res, err } for _, resource := range resList.APIResources { diff --git a/pkg/ignore/rules.go b/pkg/ignore/rules.go index 3511c2d40..a8160da2a 100644 --- a/pkg/ignore/rules.go +++ b/pkg/ignore/rules.go @@ -176,7 +176,7 @@ func (r *Rules) parseRule(rule string) error { rule = after ok, err := filepath.Match(rule, n) if err != nil { - slog.Error("failed to compile", "rule", rule, slog.Any("error", err)) + slog.Error("failed to compile", slog.String("rule", rule), slog.Any("error", err)) return false } return ok @@ -186,7 +186,11 @@ 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, slog.Any("error", err)) + slog.Error( + "failed to compile", + slog.String("rule", rule), + slog.Any("error", err), + ) return false } return ok @@ -198,7 +202,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, slog.Any("error", err)) + slog.Error("failed to compile", slog.String("rule", rule), slog.Any("error", err)) return false } return ok diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 47a226000..be98dac31 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -563,7 +563,12 @@ func (c *Client) update(originals, targets ResourceList, createApplyFunc CreateA } kind := target.Mapping.GroupVersionKind.Kind - c.Logger().Debug("created a new resource", "namespace", target.Namespace, "name", target.Name, "kind", kind) + c.Logger().Debug( + "created a new resource", + slog.String("namespace", target.Namespace), + slog.String("name", target.Name), + slog.String("kind", kind), + ) return nil } @@ -594,19 +599,37 @@ func (c *Client) update(originals, targets ResourceList, createApplyFunc CreateA c.Logger().Debug("deleting resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind) if err := info.Get(); err != nil { - c.Logger().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", + slog.String("namespace", info.Namespace), + slog.String("name", info.Name), + slog.String("kind", info.Mapping.GroupVersionKind.Kind), + slog.Any("error", err), + ) continue } annotations, err := metadataAccessor.Annotations(info.Object) if err != nil { - c.Logger().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", + slog.String("namespace", info.Namespace), + slog.String("name", info.Name), + slog.String("kind", info.Mapping.GroupVersionKind.Kind), + slog.Any("error", err), + ) } if annotations != nil && annotations[ResourcePolicyAnno] == KeepPolicy { c.Logger().Debug("skipping delete due to annotation", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, "annotation", ResourcePolicyAnno, "value", KeepPolicy) continue } if err := deleteResource(info, metav1.DeletePropagationBackground); err != nil { - c.Logger().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", + slog.String("namespace", info.Namespace), + slog.String("name", info.Name), + slog.String("kind", info.Mapping.GroupVersionKind.Kind), + slog.Any("error", err), + ) if !apierrors.IsNotFound(err) { updateErrors = append(updateErrors, fmt.Errorf("failed to delete resource %s: %w", info.Name, err)) } @@ -760,7 +783,13 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate slog.String("fieldValidationDirective", string(updateOptions.fieldValidationDirective))) return func(original, target *resource.Info) error { if err := replaceResource(target, updateOptions.fieldValidationDirective); err != nil { - c.Logger().Debug("error replacing the resource", "namespace", target.Namespace, "name", target.Name, "kind", target.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) + c.Logger().With( + slog.String("namespace", target.Namespace), + slog.String("name", target.Name), + slog.String("gvk", target.Mapping.GroupVersionKind.String()), + ).Debug( + "error replacing the resource", slog.Any("error", err), + ) return err } @@ -829,7 +858,12 @@ func (c *Client) Delete(resources ResourceList, policy metav1.DeletionPropagatio err := deleteResource(target, policy) if err == nil || apierrors.IsNotFound(err) { if err != nil { - c.Logger().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", + slog.String("namespace", target.Namespace), + slog.String("name", target.Name), + slog.String("kind", target.Mapping.GroupVersionKind.Kind), + slog.Any("error", err)) } mtx.Lock() defer mtx.Unlock() diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go index f776ae471..354915b29 100644 --- a/pkg/kube/wait.go +++ b/pkg/kube/wait.go @@ -102,11 +102,20 @@ func (hw *legacyWaiter) isRetryableError(err error, resource *resource.Info) boo if err == nil { return false } - slog.Debug("error received when checking resource status", "resource", resource.Name, slog.Any("error", err)) + slog.Debug( + "error received when checking resource status", + slog.String("resource", resource.Name), + slog.Any("error", err), + ) if ev, ok := err.(*apierrors.StatusError); ok { statusCode := ev.Status().Code retryable := hw.isRetryableHTTPStatusCode(statusCode) - slog.Debug("status code received", "resource", resource.Name, "statusCode", statusCode, "retryable", retryable) + slog.Debug( + "status code received", + slog.String("resource", resource.Name), + slog.Int("statusCode", int(statusCode)), + slog.Bool("retryable", retryable), + ) return retryable } slog.Debug("retryable error assumed", "resource", resource.Name) @@ -137,9 +146,9 @@ func (hw *legacyWaiter) WaitForDelete(deleted ResourceList, timeout time.Duratio elapsed := time.Since(startTime).Round(time.Second) if err != nil { - slog.Debug("wait for resources failed", "elapsed", elapsed, slog.Any("error", err)) + slog.Debug("wait for resources failed", slog.Duration("elapsed", elapsed), slog.Any("error", err)) } else { - slog.Debug("wait for resources succeeded", "elapsed", elapsed) + slog.Debug("wait for resources succeeded", slog.Duration("elapsed", elapsed)) } return err diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go index 5af432d8a..f82ade5e9 100644 --- a/pkg/storage/driver/cfgmaps.go +++ b/pkg/storage/driver/cfgmaps.go @@ -75,13 +75,13 @@ func (cfgmaps *ConfigMaps) Get(key string) (release.Releaser, error) { return nil, ErrReleaseNotFound } - cfgmaps.Logger().Debug("failed to get release", "key", key, slog.Any("error", err)) + cfgmaps.Logger().Debug("failed to get release", slog.String("key", key), slog.Any("error", err)) return nil, err } // found the configmap, decode the base64 data string r, err := decodeRelease(obj.Data["release"]) if err != nil { - cfgmaps.Logger().Debug("failed to decode data", "key", key, slog.Any("error", err)) + cfgmaps.Logger().Debug("failed to decode data", slog.String("key", key), slog.Any("error", err)) return nil, err } r.Labels = filterSystemLabels(obj.Labels) @@ -109,7 +109,7 @@ func (cfgmaps *ConfigMaps) List(filter func(release.Releaser) bool) ([]release.R for _, item := range list.Items { rls, err := decodeRelease(item.Data["release"]) if err != nil { - cfgmaps.Logger().Debug("failed to decode release", "item", item, slog.Any("error", err)) + cfgmaps.Logger().Debug("failed to decode release", slog.Any("item", item), slog.Any("error", err)) continue } @@ -181,7 +181,7 @@ func (cfgmaps *ConfigMaps) Create(key string, rls release.Releaser) error { // create a new configmap to hold the release obj, err := newConfigMapsObject(key, rel, lbs) if err != nil { - cfgmaps.Logger().Debug("failed to encode release", "name", rac.Name(), slog.Any("error", err)) + cfgmaps.Logger().Debug("failed to encode release", slog.String("name", rac.Name()), slog.Any("error", err)) return err } // push the configmap object out into the kubiverse @@ -214,7 +214,11 @@ func (cfgmaps *ConfigMaps) Update(key string, rel release.Releaser) error { // create a new configmap object to hold the release obj, err := newConfigMapsObject(key, rls, lbs) if err != nil { - cfgmaps.Logger().Debug("failed to encode release", "name", rls.Name, slog.Any("error", err)) + cfgmaps.Logger().Debug( + "failed to encode release", + slog.String("name", rls.Name), + slog.Any("error", err), + ) return err } // push the configmap object out into the kubiverse diff --git a/pkg/storage/driver/secrets.go b/pkg/storage/driver/secrets.go index 85f3497e7..a73f3cf05 100644 --- a/pkg/storage/driver/secrets.go +++ b/pkg/storage/driver/secrets.go @@ -103,7 +103,10 @@ func (secrets *Secrets) List(filter func(release.Releaser) bool) ([]release.Rele for _, item := range list.Items { rls, err := decodeRelease(string(item.Data["release"])) if err != nil { - secrets.Logger().Debug("list failed to decode release", "key", item.Name, slog.Any("error", err)) + secrets.Logger().Debug( + "list failed to decode release", slog.String("key", item.Name), + slog.Any("error", err), + ) continue } @@ -142,7 +145,11 @@ func (secrets *Secrets) Query(labels map[string]string) ([]release.Releaser, err for _, item := range list.Items { rls, err := decodeRelease(string(item.Data["release"])) if err != nil { - secrets.Logger().Debug("failed to decode release", "key", item.Name, slog.Any("error", err)) + secrets.Logger().Debug( + "failed to decode release", + slog.String("key", item.Name), + slog.Any("error", err), + ) continue } rls.Labels = item.Labels diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go index b6ea3916d..21d9f6679 100644 --- a/pkg/storage/driver/sql.go +++ b/pkg/storage/driver/sql.go @@ -319,18 +319,23 @@ func (s *SQL) Get(key string) (release.Releaser, error) { // Get will return an error if the result is empty if err := s.db.Get(&record, query, args...); err != nil { - s.Logger().Debug("got SQL error when getting release", "key", key, slog.Any("error", err)) + s.Logger().Debug("got SQL error when getting release", slog.String("key", key), slog.Any("error", err)) return nil, ErrReleaseNotFound } release, err := decodeRelease(record.Body) if err != nil { - s.Logger().Debug("failed to decode data", "key", key, slog.Any("error", err)) + s.Logger().Debug("failed to decode data", slog.String("key", key), slog.Any("error", err)) return nil, err } if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil { - s.Logger().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", + slog.String("namespace", s.namespace), + slog.String("key", key), + slog.Any("error", err), + ) return nil, err } @@ -365,12 +370,17 @@ func (s *SQL) List(filter func(release.Releaser) bool) ([]release.Releaser, erro for _, record := range records { release, err := decodeRelease(record.Body) if err != nil { - s.Logger().Debug("failed to decode release", "record", record, slog.Any("error", err)) + s.Logger().Debug("failed to decode release", slog.Any("record", record), slog.Any("error", err)) continue } if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil { - s.Logger().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", + slog.String("namespace", record.Namespace), + slog.String("key", record.Key), + slog.Any("error", err), + ) return nil, err } maps.Copy(release.Labels, getReleaseSystemLabels(release)) @@ -429,12 +439,17 @@ func (s *SQL) Query(labels map[string]string) ([]release.Releaser, error) { for _, record := range records { release, err := decodeRelease(record.Body) if err != nil { - s.Logger().Debug("failed to decode release", "record", record, slog.Any("error", err)) + s.Logger().Debug("failed to decode release", slog.Any("record", record), slog.Any("error", err)) continue } if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil { - s.Logger().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", + slog.String("namespace", record.Namespace), + slog.String("key", record.Key), + slog.Any("error", err), + ) return nil, err } @@ -518,11 +533,11 @@ func (s *SQL) Create(key string, rel release.Releaser) error { var record SQLReleaseWrapper if err := transaction.Get(&record, selectQuery, args...); err == nil { - s.Logger().Debug("release already exists", "key", key) + s.Logger().Debug("release already exists", slog.String("key", key)) return ErrReleaseExists } - s.Logger().Debug("failed to store release in SQL database", "key", key, slog.Any("error", err)) + s.Logger().Debug("failed to store release in SQL database", slog.String("key", key), slog.Any("error", err)) return err } @@ -596,7 +611,7 @@ func (s *SQL) Update(key string, rel release.Releaser) error { } if _, err := s.db.Exec(query, args...); err != nil { - s.Logger().Debug("failed to update release in SQL database", "key", key, slog.Any("error", err)) + s.Logger().Debug("failed to update release in SQL database", slog.String("key", key), slog.Any("error", err)) return err } @@ -625,13 +640,13 @@ func (s *SQL) Delete(key string) (release.Releaser, error) { var record SQLReleaseWrapper err = transaction.Get(&record, selectQuery, args...) if err != nil { - s.Logger().Debug("release not found", "key", key, slog.Any("error", err)) + s.Logger().Debug("release not found", slog.String("key", key), slog.Any("error", err)) return nil, ErrReleaseNotFound } release, err := decodeRelease(record.Body) if err != nil { - s.Logger().Debug("failed to decode release", "key", key, slog.Any("error", err)) + s.Logger().Debug("failed to decode release", slog.String("key", key), slog.Any("error", err)) transaction.Rollback() return nil, err } @@ -654,7 +669,11 @@ func (s *SQL) Delete(key string) (release.Releaser, error) { } if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil { - s.Logger().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", + slog.String("namespace", s.namespace), + slog.String("key", key), + slog.Any("error", err)) return nil, err } diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 07dc12c7b..d6c41635b 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -293,7 +293,7 @@ func (s *Storage) deleteReleaseVersion(name string, version int) error { key := makeKey(name, version) _, err := s.Delete(name, version) if err != nil { - s.Logger().Debug("error pruning release", "key", key, slog.Any("error", err)) + s.Logger().Debug("error pruning release", slog.String("key", key), slog.Any("error", err)) return err } return nil From b825a18362806e1810dc5eac41d8475c1d3c4cd8 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Tue, 9 Dec 2025 14:08:34 -0500 Subject: [PATCH 074/571] Update the govulncheck.yml to run on change Signed-off-by: Matt Farina --- .github/workflows/govulncheck.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index a3088ad75..59fc695a0 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -3,6 +3,7 @@ on: push: paths: - go.sum + - .github/workflows/govulncheck.yml schedule: - cron: "0 0 * * *" From bc9462f20fc9a948fe557c87c895c06d4c0ddc6a Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Tue, 9 Dec 2025 13:52:50 -0500 Subject: [PATCH 075/571] Fix govulncheck in CI govulncheck is having trouble checking out the source due to multiple Authorization headers. The fix for this is to not persist the credentials. Signed-off-by: Matt Farina --- .github/workflows/govulncheck.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 59fc695a0..992283d1a 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -16,6 +16,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 + with: + persist-credentials: false - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go From 24a82589f8ee9f8f385c63b6c68dabdf19109da7 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Tue, 9 Dec 2025 15:47:56 -0500 Subject: [PATCH 076/571] Run the vulnerability check on PR that change the file Signed-off-by: Matt Farina --- .github/workflows/govulncheck.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 992283d1a..e8f2560e3 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -4,6 +4,10 @@ on: paths: - go.sum - .github/workflows/govulncheck.yml + pull_request: + paths: + - go.sum + - .github/workflows/govulncheck.yml schedule: - cron: "0 0 * * *" From d195cfa2e338ac7a5f32052bb099ea176ea9342a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 20:04:53 +0000 Subject: [PATCH 077/571] chore(deps): bump golang.org/x/term from 0.37.0 to 0.38.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.37.0 to 0.38.0. - [Commits](https://github.com/golang/term/compare/v0.37.0...v0.38.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-version: 0.38.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 0561bcc44..f437ae8cf 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/tetratelabs/wazero v1.10.1 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.45.0 - golang.org/x/term v0.37.0 + golang.org/x/term v0.38.0 golang.org/x/text v0.31.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.34.2 @@ -166,7 +166,7 @@ require ( golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect + golang.org/x/sys v0.39.0 // indirect golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.38.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect diff --git a/go.sum b/go.sum index cf09e6b3c..3b88d8749 100644 --- a/go.sum +++ b/go.sum @@ -454,8 +454,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -463,8 +463,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= From a9bbffb4b67bdf05e0f5f9671f4ee5d7229a7494 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 14:28:40 +0000 Subject: [PATCH 078/571] chore(deps): bump github.com/spf13/cobra from 1.10.1 to 1.10.2 Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.10.1 to 1.10.2. - [Release notes](https://github.com/spf13/cobra/releases) - [Commits](https://github.com/spf13/cobra/compare/v1.10.1...v1.10.2) --- updated-dependencies: - dependency-name: github.com/spf13/cobra dependency-version: 1.10.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f437ae8cf..ccbbda0ab 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/opencontainers/image-spec v1.1.1 github.com/rubenv/sql-migrate v1.8.0 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 - github.com/spf13/cobra v1.10.1 + github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 github.com/tetratelabs/wazero v1.10.1 diff --git a/go.sum b/go.sum index 3b88d8749..363d716b3 100644 --- a/go.sum +++ b/go.sum @@ -304,8 +304,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= From 2dc581dc1c9c07e36dde9acc70bd86e23963662a Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Thu, 11 Dec 2025 14:45:31 +0000 Subject: [PATCH 079/571] fix(upgrade): pass --server-side flag to install when using upgrade --install When running `helm upgrade --install` on a non-existent release, the --server-side flag was not being passed to the install action. This caused the install to always use server-side apply (the default), ignoring --server-side=false. Copy ServerSideApply and ForceConflicts from the upgrade client to the install client when falling back to install. Fixes #31627 Signed-off-by: Evans Mungai --- pkg/cmd/upgrade.go | 2 ++ pkg/cmd/upgrade_test.go | 55 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index 92f130f60..b6fa12e43 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -153,6 +153,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { instClient.EnableDNS = client.EnableDNS instClient.HideSecret = client.HideSecret instClient.TakeOwnership = client.TakeOwnership + instClient.ForceConflicts = client.ForceConflicts + instClient.ServerSideApply = client.ServerSideApply != "false" if isReleaseUninstalled(versions) { instClient.Replace = true diff --git a/pkg/cmd/upgrade_test.go b/pkg/cmd/upgrade_test.go index 8729be0ec..0ae1e3561 100644 --- a/pkg/cmd/upgrade_test.go +++ b/pkg/cmd/upgrade_test.go @@ -605,3 +605,58 @@ func TestUpgradeWithDryRun(t *testing.T) { t.Error("expected error when --hide-secret used without --dry-run") } } + +func TestUpgradeInstallServerSideApply(t *testing.T) { + _, _, chartPath := prepareMockRelease(t, "ssa-test") + + defer resetEnv()() + + tests := []struct { + name string + serverSideFlag string + expectedApplyMethod string + }{ + { + name: "upgrade --install with --server-side=false uses client-side apply", + serverSideFlag: "--server-side=false", + expectedApplyMethod: "csa", + }, + { + name: "upgrade --install with --server-side=true uses server-side apply", + serverSideFlag: "--server-side=true", + expectedApplyMethod: "ssa", + }, + { + name: "upgrade --install with --server-side=auto uses server-side apply (default for new install)", + serverSideFlag: "--server-side=auto", + expectedApplyMethod: "ssa", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + store := storageFixture() + releaseName := fmt.Sprintf("ssa-test-%s", tt.expectedApplyMethod) + + cmd := fmt.Sprintf("upgrade %s --install %s '%s'", releaseName, tt.serverSideFlag, chartPath) + _, _, err := executeActionCommandC(store, cmd) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + rel, err := store.Get(releaseName, 1) + if err != nil { + t.Fatalf("unexpected error getting release: %v", err) + } + + relV1, err := releaserToV1Release(rel) + if err != nil { + t.Fatalf("unexpected error converting release: %v", err) + } + + if relV1.ApplyMethod != tt.expectedApplyMethod { + t.Errorf("expected ApplyMethod %q, got %q", tt.expectedApplyMethod, relV1.ApplyMethod) + } + }) + } +} From ea52f873988d129668417aebc40782055b67ca48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:07:22 +0000 Subject: [PATCH 080/571] chore(deps): bump github.com/rubenv/sql-migrate from 1.8.0 to 1.8.1 Bumps [github.com/rubenv/sql-migrate](https://github.com/rubenv/sql-migrate) from 1.8.0 to 1.8.1. - [Commits](https://github.com/rubenv/sql-migrate/compare/v1.8.0...v1.8.1) --- updated-dependencies: - dependency-name: github.com/rubenv/sql-migrate dependency-version: 1.8.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ccbbda0ab..9c540049a 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 - github.com/rubenv/sql-migrate v1.8.0 + github.com/rubenv/sql-migrate v1.8.1 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 diff --git a/go.sum b/go.sum index 363d716b3..f04ca40b3 100644 --- a/go.sum +++ b/go.sum @@ -289,8 +289,8 @@ github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0 github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o= -github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw= +github.com/rubenv/sql-migrate v1.8.1 h1:EPNwCvjAowHI3TnZ+4fQu3a915OpnQoPAjTXCGOy2U0= +github.com/rubenv/sql-migrate v1.8.1/go.mod h1:BTIKBORjzyxZDS6dzoiw6eAFYJ1iNlGAtjn4LGeVjS8= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= From 8f636b5bd229c071ab5f0237a7b4553337392b44 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Thu, 11 Dec 2025 10:30:22 -0500 Subject: [PATCH 081/571] Use latest patch release of Go in releases GitHub Actions has a cache of tools, like Go, and it does not update this cache when a new version comes out. It can take more than a week for a new version to be available. This change forces the action to check if a newer version is available than is in the cache. Closes #31634 Signed-off-by: Matt Farina --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 34b244086..cf8595742 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,6 +31,7 @@ jobs: uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # pin@6.1.0 with: go-version: '${{ env.GOLANG_VERSION }}' + check-latest: true - name: Run unit tests run: make test-coverage - name: Build Helm Binaries From e900a255ef4cba78c87f680c52f5b8be5d617277 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Thu, 11 Dec 2025 10:42:04 -0700 Subject: [PATCH 082/571] feat: move TerryHowe triage to maintainers Signed-off-by: Terry Howe --- OWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OWNERS b/OWNERS index 761cf76a3..17dd3997c 100644 --- a/OWNERS +++ b/OWNERS @@ -7,9 +7,9 @@ maintainers: - sabre1041 - scottrigby - technosophos + - TerryHowe triage: - banjoh - - TerryHowe - yxxhero - zonggen - z4ce From 7fb17286fa64fb8f7d3728247725e7f453d29da5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 21:17:55 +0000 Subject: [PATCH 083/571] chore(deps): bump golang.org/x/text from 0.31.0 to 0.32.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.31.0 to 0.32.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.31.0...v0.32.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.32.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 9c540049a..06dd6f32d 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.45.0 golang.org/x/term v0.38.0 - golang.org/x/text v0.31.0 + golang.org/x/text v0.32.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.34.2 k8s.io/apiextensions-apiserver v0.34.2 @@ -162,13 +162,13 @@ require ( go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/mod v0.29.0 // indirect + golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.18.0 // indirect + golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.38.0 // indirect + golang.org/x/tools v0.39.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/grpc v1.72.1 // indirect diff --git a/go.sum b/go.sum index f04ca40b3..da00cbd1d 100644 --- a/go.sum +++ b/go.sum @@ -404,8 +404,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -433,8 +433,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -472,8 +472,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -484,8 +484,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 858cf315830dd4818297e00d8098e6f79422d306 Mon Sep 17 00:00:00 2001 From: wangjingcun Date: Fri, 12 Dec 2025 12:01:35 +0800 Subject: [PATCH 084/571] chore: fix some comments to improve readability Signed-off-by: wangjingcun --- internal/chart/v3/lint/rules/deprecations.go | 2 +- internal/chart/v3/loader/load.go | 2 +- pkg/action/install.go | 4 ++-- pkg/chart/v2/lint/rules/deprecations.go | 2 +- pkg/chart/v2/loader/load.go | 2 +- pkg/kube/client.go | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/chart/v3/lint/rules/deprecations.go b/internal/chart/v3/lint/rules/deprecations.go index 6f86bdbbd..b088dda2b 100644 --- a/internal/chart/v3/lint/rules/deprecations.go +++ b/internal/chart/v3/lint/rules/deprecations.go @@ -36,7 +36,7 @@ var ( k8sVersionMinor = "20" ) -// deprecatedAPIError indicates than an API is deprecated in Kubernetes +// deprecatedAPIError indicates that an API is deprecated in Kubernetes type deprecatedAPIError struct { Deprecated string Message string diff --git a/internal/chart/v3/loader/load.go b/internal/chart/v3/loader/load.go index 1c5b4cad1..9d657fb95 100644 --- a/internal/chart/v3/loader/load.go +++ b/internal/chart/v3/loader/load.go @@ -181,7 +181,7 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { // LoadValues loads values from a reader. // // The reader is expected to contain one or more YAML documents, the values of which are merged. -// And the values can be either a chart's default values or a user-supplied values. +// And the values can be either a chart's default values or user-supplied values. func LoadValues(data io.Reader) (map[string]interface{}, error) { values := map[string]interface{}{} reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) diff --git a/pkg/action/install.go b/pkg/action/install.go index 2f5910284..ecf3ea340 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -62,8 +62,8 @@ import ( "helm.sh/helm/v4/pkg/storage/driver" ) -// notesFileSuffix that we want to treat special. It goes through the templating engine -// but it's not a yaml file (resource) hence can't have hooks, etc. And the user actually +// notesFileSuffix that we want to treat specially. It goes through the templating engine +// but it's not a YAML file (resource) hence can't have hooks, etc. And the user actually // wants to see this file after rendering in the status command. However, it must be a suffix // since there can be filepath in front of it. const notesFileSuffix = "NOTES.txt" diff --git a/pkg/chart/v2/lint/rules/deprecations.go b/pkg/chart/v2/lint/rules/deprecations.go index 6eba316bc..34bd361bf 100644 --- a/pkg/chart/v2/lint/rules/deprecations.go +++ b/pkg/chart/v2/lint/rules/deprecations.go @@ -36,7 +36,7 @@ var ( k8sVersionMinor = "20" ) -// deprecatedAPIError indicates than an API is deprecated in Kubernetes +// deprecatedAPIError indicates that an API is deprecated in Kubernetes type deprecatedAPIError struct { Deprecated string Message string diff --git a/pkg/chart/v2/loader/load.go b/pkg/chart/v2/loader/load.go index ba3a9b6bc..6fdaa4f7d 100644 --- a/pkg/chart/v2/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -208,7 +208,7 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { // LoadValues loads values from a reader. // // The reader is expected to contain one or more YAML documents, the values of which are merged. -// And the values can be either a chart's default values or a user-supplied values. +// And the values can be either a chart's default values or user-supplied values. func LoadValues(data io.Reader) (map[string]interface{}, error) { values := map[string]interface{}{} reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 47a226000..136968e19 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -271,7 +271,7 @@ func ClientCreateOptionDryRun(dryRun bool) ClientCreateOption { } } -// ClientCreateOptionFieldValidationDirective specifies show API operations validate object's schema +// ClientCreateOptionFieldValidationDirective specifies how API operations validate object's schema // - For client-side apply: this is ignored // - For server-side apply: the directive is sent to the server to perform the validation // @@ -681,7 +681,7 @@ func ClientUpdateOptionDryRun(dryRun bool) ClientUpdateOption { } } -// ClientUpdateOptionFieldValidationDirective specifies show API operations validate object's schema +// ClientUpdateOptionFieldValidationDirective specifies how API operations validate object's schema // - For client-side apply: this is ignored // - For server-side apply: the directive is sent to the server to perform the validation // From e9a0510b0298f4800991717e21d1c536a4d46dbb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:54:28 +0000 Subject: [PATCH 085/571] chore(deps): bump golang.org/x/crypto from 0.45.0 to 0.46.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.45.0 to 0.46.0. - [Commits](https://github.com/golang/crypto/compare/v0.45.0...v0.46.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.46.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 06dd6f32d..b344fa516 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/tetratelabs/wazero v1.10.1 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.45.0 + golang.org/x/crypto v0.46.0 golang.org/x/term v0.38.0 golang.org/x/text v0.32.0 gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index da00cbd1d..4de45a3da 100644 --- a/go.sum +++ b/go.sum @@ -396,8 +396,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= From 1f8e84dff250905d7fff1320beff190a86d21ab9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:54:38 +0000 Subject: [PATCH 086/571] chore(deps): bump the k8s-io group with 7 updates Bumps the k8s-io group with 7 updates: | Package | From | To | | --- | --- | --- | | [k8s.io/api](https://github.com/kubernetes/api) | `0.34.2` | `0.34.3` | | [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) | `0.34.2` | `0.34.3` | | [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) | `0.34.2` | `0.34.3` | | [k8s.io/apiserver](https://github.com/kubernetes/apiserver) | `0.34.2` | `0.34.3` | | [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime) | `0.34.2` | `0.34.3` | | [k8s.io/client-go](https://github.com/kubernetes/client-go) | `0.34.2` | `0.34.3` | | [k8s.io/kubectl](https://github.com/kubernetes/kubectl) | `0.34.2` | `0.34.3` | Updates `k8s.io/api` from 0.34.2 to 0.34.3 - [Commits](https://github.com/kubernetes/api/compare/v0.34.2...v0.34.3) Updates `k8s.io/apiextensions-apiserver` from 0.34.2 to 0.34.3 - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.34.2...v0.34.3) Updates `k8s.io/apimachinery` from 0.34.2 to 0.34.3 - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.34.2...v0.34.3) Updates `k8s.io/apiserver` from 0.34.2 to 0.34.3 - [Commits](https://github.com/kubernetes/apiserver/compare/v0.34.2...v0.34.3) Updates `k8s.io/cli-runtime` from 0.34.2 to 0.34.3 - [Commits](https://github.com/kubernetes/cli-runtime/compare/v0.34.2...v0.34.3) Updates `k8s.io/client-go` from 0.34.2 to 0.34.3 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.34.2...v0.34.3) Updates `k8s.io/kubectl` from 0.34.2 to 0.34.3 - [Commits](https://github.com/kubernetes/kubectl/compare/v0.34.2...v0.34.3) --- updated-dependencies: - dependency-name: k8s.io/api dependency-version: 0.34.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apiextensions-apiserver dependency-version: 0.34.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apimachinery dependency-version: 0.34.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apiserver dependency-version: 0.34.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/cli-runtime dependency-version: 0.34.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/client-go dependency-version: 0.34.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/kubectl dependency-version: 0.34.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io ... Signed-off-by: dependabot[bot] --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 06dd6f32d..85a497628 100644 --- a/go.mod +++ b/go.mod @@ -39,14 +39,14 @@ require ( golang.org/x/term v0.38.0 golang.org/x/text v0.32.0 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 - k8s.io/apiextensions-apiserver v0.34.2 - k8s.io/apimachinery v0.34.2 - k8s.io/apiserver v0.34.2 - k8s.io/cli-runtime v0.34.2 - k8s.io/client-go v0.34.2 + k8s.io/api v0.34.3 + k8s.io/apiextensions-apiserver v0.34.3 + k8s.io/apimachinery v0.34.3 + k8s.io/apiserver v0.34.3 + k8s.io/cli-runtime v0.34.3 + k8s.io/client-go v0.34.3 k8s.io/klog/v2 v2.130.1 - k8s.io/kubectl v0.34.2 + k8s.io/kubectl v0.34.3 oras.land/oras-go/v2 v2.6.0 sigs.k8s.io/controller-runtime v0.22.4 sigs.k8s.io/kustomize/kyaml v0.21.0 @@ -176,7 +176,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/component-base v0.34.2 // indirect + k8s.io/component-base v0.34.3 // indirect k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect diff --git a/go.sum b/go.sum index da00cbd1d..5974913c9 100644 --- a/go.sum +++ b/go.sum @@ -512,26 +512,26 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= -k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= -k8s.io/cli-runtime v0.34.2 h1:cct1GEuWc3IyVT8MSCoIWzRGw9HJ/C5rgP32H60H6aE= -k8s.io/cli-runtime v0.34.2/go.mod h1:X13tsrYexYUCIq8MarCBy8lrm0k0weFPTpcaNo7lms4= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= -k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= +k8s.io/cli-runtime v0.34.3 h1:YRyMhiwX0dT9lmG0AtZDaeG33Nkxgt9OlCTZhRXj9SI= +k8s.io/cli-runtime v0.34.3/go.mod h1:GVwL1L5uaGEgM7eGeKjaTG2j3u134JgG4dAI6jQKhMc= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= -k8s.io/kubectl v0.34.2 h1:+fWGrVlDONMUmmQLDaGkQ9i91oszjjRAa94cr37hzqA= -k8s.io/kubectl v0.34.2/go.mod h1:X2KTOdtZZNrTWmUD4oHApJ836pevSl+zvC5sI6oO2YQ= +k8s.io/kubectl v0.34.3 h1:vpM6//153gh5gvsYHXWHVJ4l4xmN5QFwTSmlfd8icm8= +k8s.io/kubectl v0.34.3/go.mod h1:zZQHtIZoUqTP1bAnPzq/3W1jfc0NeOeunFgcswrfg1c= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= From 292fe702193e8ba9ce4c8ffffdd90cdfa761501c Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Fri, 12 Dec 2025 16:32:32 +0000 Subject: [PATCH 087/571] fix(values): preserve nil values when chart default is empty map Only delete nil user values when overriding a non-nil chart default. When chart has empty map or no default for a key, preserve user's nil. | Scenario | Result | |----------|--------| | User sets `baz: ~`, chart has `baz: "value"` | Key deleted | | User sets `baz: ~`, chart has empty map `{}` | Nil preserved | | User sets `baz: ~`, chart has `baz: ~` | Nil preserved | Fixes #31643 Signed-off-by: Evans Mungai --- pkg/chart/common/util/coalesce.go | 18 +- pkg/chart/common/util/coalesce_test.go | 255 +++++++++++++------------ 2 files changed, 145 insertions(+), 128 deletions(-) diff --git a/pkg/chart/common/util/coalesce.go b/pkg/chart/common/util/coalesce.go index 07794a04a..3220a275c 100644 --- a/pkg/chart/common/util/coalesce.go +++ b/pkg/chart/common/util/coalesce.go @@ -302,23 +302,23 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, pref if dst == nil { return src } - for key, val := range dst { - if val == nil { - src[key] = nil - } - } // Because dest has higher precedence than src, dest values override src // values. for key, val := range src { fullkey := concatPrefix(prefix, key) - if dv, ok := dst[key]; ok && !merge && dv == nil { - delete(dst, key) - } else if !ok { + dv, ok := dst[key] + if !ok { dst[key] = val + } else if dv == nil && !merge && val != nil { + // When coalescing (not merging), if dst has nil and src has a non-nil + // value, the user is nullifying a chart default - remove the key. + // Per Helm docs: setting a key to null deletes it. + // But if src also has nil (or key not in src), preserve the nil (issue #31643). + delete(dst, key) } else if istable(val) { if istable(dv) { coalesceTablesFullKey(printf, dv.(map[string]interface{}), val.(map[string]interface{}), fullkey, merge) - } else { + } else if dv != nil { printf("warning: cannot overwrite table with non table for %s (%v)", fullkey, val) } } else if istable(dv) && val != nil { diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index 871bfa8da..598f1f9ef 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -25,6 +25,7 @@ import ( "text/template" "github.com/stretchr/testify/assert" + req "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" @@ -401,131 +402,113 @@ func TestMergeValues(t *testing.T) { } func TestCoalesceTables(t *testing.T) { - dst := map[string]interface{}{ - "name": "Ishmael", - "address": map[string]interface{}{ - "street": "123 Spouter Inn Ct.", - "city": "Nantucket", - "country": nil, - }, - "details": map[string]interface{}{ - "friends": []string{"Tashtego"}, - }, - "boat": "pequod", - "hole": nil, - } - src := map[string]interface{}{ - "occupation": "whaler", - "address": map[string]interface{}{ - "state": "MA", - "street": "234 Spouter Inn Ct.", - "country": "US", - }, - "details": "empty", - "boat": map[string]interface{}{ - "mast": true, - }, - "hole": "black", - } - - // What we expect is that anything in dst overrides anything in src, but that - // otherwise the values are coalesced. - CoalesceTables(dst, src) - - if dst["name"] != "Ishmael" { - t.Errorf("Unexpected name: %s", dst["name"]) - } - if dst["occupation"] != "whaler" { - t.Errorf("Unexpected occupation: %s", dst["occupation"]) - } - - addr, ok := dst["address"].(map[string]interface{}) - if !ok { - t.Fatal("Address went away.") - } - - if addr["street"].(string) != "123 Spouter Inn Ct." { - t.Errorf("Unexpected address: %v", addr["street"]) - } - - if addr["city"].(string) != "Nantucket" { - t.Errorf("Unexpected city: %v", addr["city"]) - } - - if addr["state"].(string) != "MA" { - t.Errorf("Unexpected state: %v", addr["state"]) - } - - if _, ok = addr["country"]; ok { - t.Error("The country is not left out.") - } - - if det, ok := dst["details"].(map[string]interface{}); !ok { - t.Fatalf("Details is the wrong type: %v", dst["details"]) - } else if _, ok := det["friends"]; !ok { - t.Error("Could not find your friends. Maybe you don't have any. :-(") - } - - if dst["boat"].(string) != "pequod" { - t.Errorf("Expected boat string, got %v", dst["boat"]) - } - - if _, ok = dst["hole"]; ok { - t.Error("The hole still exists.") - } - - dst2 := map[string]interface{}{ - "name": "Ishmael", - "address": map[string]interface{}{ - "street": "123 Spouter Inn Ct.", - "city": "Nantucket", - "country": "US", - }, - "details": map[string]interface{}{ - "friends": []string{"Tashtego"}, - }, - "boat": "pequod", - "hole": "black", - } - - // What we expect is that anything in dst should have all values set, - // this happens when the --reuse-values flag is set but the chart has no modifications yet - CoalesceTables(dst2, nil) - - if dst2["name"] != "Ishmael" { - t.Errorf("Unexpected name: %s", dst2["name"]) - } - - addr2, ok := dst2["address"].(map[string]interface{}) - if !ok { - t.Fatal("Address went away.") - } + is := assert.New(t) + t.Run("case 1", func(t *testing.T) { + dst := map[string]interface{}{ + "name": "Ishmael", + "address": map[string]interface{}{ + "street": "123 Spouter Inn Ct.", + "city": "Nantucket", + "country": nil, + }, + "details": map[string]interface{}{ + "friends": []string{"Tashtego"}, + }, + "boat": "pequod", + "hole": nil, + } + src := map[string]interface{}{ + "occupation": "whaler", + "address": map[string]interface{}{ + "state": "MA", + "street": "234 Spouter Inn Ct.", + "country": "US", + }, + "details": "empty", + "boat": map[string]interface{}{ + "mast": true, + }, + "hole": "black", + } - if addr2["street"].(string) != "123 Spouter Inn Ct." { - t.Errorf("Unexpected address: %v", addr2["street"]) - } + // What we expect is that anything in dst overrides anything in src, but that + // otherwise the values are coalesced. + CoalesceTables(dst, src) + + is.Equal("Ishmael", dst["name"], "Unexpected name: %s", dst["name"]) + is.Equal("whaler", dst["occupation"], "Unexpected occupation: %s", dst["occupation"]) + + addr, ok := dst["address"].(map[string]interface{}) + req.True(t, ok, "Address went away.") + + is.Equal("123 Spouter Inn Ct.", addr["street"], "Unexpected address: %v", addr["street"]) + is.Equal("Nantucket", addr["city"], "Unexpected city: %v", addr["city"]) + is.Equal("MA", addr["state"], "Unexpected state: %v", addr["state"]) + _, ok = addr["country"] + is.False(ok, "The country should be removed") + + det, ok := dst["details"].(map[string]interface{}) + req.True(t, ok, "Details is the wrong type: %v", dst["details"]) + _, ok = det["friends"] + is.True(ok, "Could not find your friends. Maybe you don't have any. :-(") + + is.Equal("pequod", dst["boat"], "Expected boat string, got %v", dst["boat"]) + _, ok = dst["hole"] + is.False(ok, "The hole should be removed") + }) + t.Run("case 2", func(t *testing.T) { + dst2 := map[string]interface{}{ + "name": "Ishmael", + "address": map[string]interface{}{ + "street": "123 Spouter Inn Ct.", + "city": "Nantucket", + "country": "US", + }, + "details": map[string]interface{}{ + "friends": []string{"Tashtego"}, + }, + "boat": "pequod", + "hole": "black", + } - if addr2["city"].(string) != "Nantucket" { - t.Errorf("Unexpected city: %v", addr2["city"]) - } + // What we expect is that anything in dst should have all values set, + // this happens when the --reuse-values flag is set but the chart has no modifications yet + CoalesceTables(dst2, nil) + + is.Equal("Ishmael", dst2["name"], "Unexpected name: %s", dst2["name"]) + addr2, ok := dst2["address"].(map[string]interface{}) + req.True(t, ok, "Address went away.") + is.Equal("123 Spouter Inn Ct.", addr2["street"], "Unexpected address: %v", addr2["street"]) + is.Equal("Nantucket", addr2["city"], "Unexpected city: %v", addr2["city"]) + is.Equal("US", addr2["country"], "Unexpected country: %v", addr2["country"]) + is.Equal("US", addr2["country"], "Unexpected country: %v", addr2["country"]) + + det2, ok := dst2["details"].(map[string]interface{}) + req.True(t, ok, "Details is the wrong type: %v", dst2["details"]) + _, ok = det2["friends"] + is.True(ok, "Could not find your friends. Maybe you don't have any. :-(") + + is.Equal("pequod", dst2["boat"], "Expected boat string, got %v", dst2["boat"]) + is.Equal("black", dst2["hole"], "Expected hole string, got %v", dst2["hole"]) + }) + t.Run("empty chart map with nil user value", func(t *testing.T) { + dst := map[string]any{ + "foo": "bar", + "baz": nil, // explicit nil from user + } - if addr2["country"].(string) != "US" { - t.Errorf("Unexpected Country: %v", addr2["country"]) - } + // Chart's default values (src - lower priority) - empty map + src := map[string]any{} - if det2, ok := dst2["details"].(map[string]interface{}); !ok { - t.Fatalf("Details is the wrong type: %v", dst2["details"]) - } else if _, ok := det2["friends"]; !ok { - t.Error("Could not find your friends. Maybe you don't have any. :-(") - } + CoalesceTables(dst, src) - if dst2["boat"].(string) != "pequod" { - t.Errorf("Expected boat string, got %v", dst2["boat"]) - } + // "foo" should be preserved + is.Equal("bar", dst["foo"]) - if dst2["hole"].(string) != "black" { - t.Errorf("Expected hole string, got %v", dst2["boat"]) - } + _, ok := dst["baz"] + is.True(ok, "Expected baz key to be present but it was removed") + is.True(dst["baz"] == nil, "Expected baz key to be nil but it is not") + }) } func TestMergeTables(t *testing.T) { @@ -731,3 +714,37 @@ func TestConcatPrefix(t *testing.T) { assert.Equal(t, "b", concatPrefix("", "b")) assert.Equal(t, "a.b", concatPrefix("a", "b")) } + +// TestCoalesceValuesEmptyMapWithNils tests the full CoalesceValues scenario +// from issue #31643 where chart has data: {} and user provides data: {foo: bar, baz: ~} +func TestCoalesceValuesEmptyMapWithNils(t *testing.T) { + is := assert.New(t) + + c := &chart.Chart{ + Metadata: &chart.Metadata{Name: "test"}, + Values: map[string]any{ + "data": map[string]any{}, // empty map in chart defaults + }, + } + + vals := map[string]any{ + "data": map[string]any{ + "foo": "bar", + "baz": nil, // explicit nil from user + }, + } + + v, err := CoalesceValues(c, vals) + is.NoError(err) + + data, ok := v["data"].(map[string]any) + is.True(ok, "data is not a map") + + // "foo" should be preserved + is.Equal("bar", data["foo"]) + + // "baz" should be preserved with nil value since it wasn't in chart defaults + _, ok = data["baz"] + is.True(ok, "Expected data.baz key to be present but it was removed") + is.Nil(data["baz"], "Expected data.baz key to be nil but it is not") +} From 679f0519804afeaa5ce8b930a30976ade2860fe0 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Fri, 12 Dec 2025 17:00:18 +0000 Subject: [PATCH 088/571] Preserve nil values in chart already Signed-off-by: Evans Mungai --- pkg/chart/common/util/coalesce.go | 27 +++++++++++++++++++------- pkg/chart/common/util/coalesce_test.go | 13 +++++++++---- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/pkg/chart/common/util/coalesce.go b/pkg/chart/common/util/coalesce.go index 3220a275c..6c72b3d56 100644 --- a/pkg/chart/common/util/coalesce.go +++ b/pkg/chart/common/util/coalesce.go @@ -302,23 +302,36 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, pref if dst == nil { return src } + // Track original non-nil src keys before modifying src + // This lets us distinguish between user nullifying a chart default vs + // user setting nil for a key not in chart defaults. + srcOriginalNonNil := make(map[string]bool) + for key, val := range src { + if val != nil { + srcOriginalNonNil[key] = true + } + } + for key, val := range dst { + if val == nil { + src[key] = nil + } + } // Because dest has higher precedence than src, dest values override src // values. for key, val := range src { fullkey := concatPrefix(prefix, key) - dv, ok := dst[key] - if !ok { - dst[key] = val - } else if dv == nil && !merge && val != nil { + if dv, ok := dst[key]; ok && !merge && dv == nil && srcOriginalNonNil[key] { // When coalescing (not merging), if dst has nil and src has a non-nil // value, the user is nullifying a chart default - remove the key. - // Per Helm docs: setting a key to null deletes it. - // But if src also has nil (or key not in src), preserve the nil (issue #31643). + // But if src also has nil (or key not in src), preserve the nil delete(dst, key) + } else if !ok { + // key not in user values, preserve src value (including nil) + dst[key] = val } else if istable(val) { if istable(dv) { coalesceTablesFullKey(printf, dv.(map[string]interface{}), val.(map[string]interface{}), fullkey, merge) - } else if dv != nil { + } else { printf("warning: cannot overwrite table with non table for %s (%v)", fullkey, val) } } else if istable(dv) && val != nil { diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index 598f1f9ef..84442e6cd 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -491,23 +491,28 @@ func TestCoalesceTables(t *testing.T) { is.Equal("pequod", dst2["boat"], "Expected boat string, got %v", dst2["boat"]) is.Equal("black", dst2["hole"], "Expected hole string, got %v", dst2["hole"]) }) - t.Run("empty chart map with nil user value", func(t *testing.T) { + t.Run("chart values with nil user value", func(t *testing.T) { dst := map[string]any{ "foo": "bar", "baz": nil, // explicit nil from user } // Chart's default values (src - lower priority) - empty map - src := map[string]any{} + src := map[string]any{ + "ben": nil, + } CoalesceTables(dst, src) // "foo" should be preserved is.Equal("bar", dst["foo"]) + _, ok := dst["ben"] + is.True(ok, "Expected ben key to be present") + is.Nil(dst["ben"], "Expected ben key to be nil but it is not") - _, ok := dst["baz"] + _, ok = dst["baz"] is.True(ok, "Expected baz key to be present but it was removed") - is.True(dst["baz"] == nil, "Expected baz key to be nil but it is not") + is.Nil(dst["baz"], "Expected baz key to be nil but it is not") }) } From 3416dd5f215a6421a70c6ab22340a96312ce8c0b Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Fri, 12 Dec 2025 17:09:18 +0000 Subject: [PATCH 089/571] Fix lint warning Signed-off-by: Evans Mungai --- pkg/chart/common/util/coalesce_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index 84442e6cd..a7d841bbc 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -402,8 +402,8 @@ func TestMergeValues(t *testing.T) { } func TestCoalesceTables(t *testing.T) { - is := assert.New(t) t.Run("case 1", func(t *testing.T) { + is := assert.New(t) dst := map[string]interface{}{ "name": "Ishmael", "address": map[string]interface{}{ @@ -456,7 +456,8 @@ func TestCoalesceTables(t *testing.T) { _, ok = dst["hole"] is.False(ok, "The hole should be removed") }) - t.Run("case 2", func(t *testing.T) { + t.Run("case 2", func(_ *testing.T) { + is := assert.New(t) dst2 := map[string]interface{}{ "name": "Ishmael", "address": map[string]interface{}{ @@ -492,6 +493,7 @@ func TestCoalesceTables(t *testing.T) { is.Equal("black", dst2["hole"], "Expected hole string, got %v", dst2["hole"]) }) t.Run("chart values with nil user value", func(t *testing.T) { + is := assert.New(t) dst := map[string]any{ "foo": "bar", "baz": nil, // explicit nil from user From 09ae0d4f2da4b602d0b94786badf2e72edce1a03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Dec 2025 21:06:25 +0000 Subject: [PATCH 090/571] chore(deps): bump actions/upload-artifact from 4.6.2 to 6.0.0 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 6.0.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/ea165f8d65b6e75b540449e92b4886f43607fa02...b7c566a772e6b6bfb58ed0dc250532a479d7789f) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 3e475edc0..514a649cb 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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: SARIF file path: results.sarif From a490bb3c20eddd68a7a009f128ffab74ba4e6587 Mon Sep 17 00:00:00 2001 From: Mads Jensen Date: Sun, 14 Dec 2025 08:54:11 +0100 Subject: [PATCH 091/571] Use errors.Is to check for io.EOF and gzip.ErrHeader In GoLang, using the == operator to check for a certain error will not unwrap the error chain, and therefore may hide the problem. Signed-off-by: Mads Jensen --- internal/chart/v3/lint/rules/crds.go | 2 +- internal/chart/v3/lint/rules/template.go | 2 +- internal/chart/v3/loader/archive.go | 2 +- internal/chart/v3/loader/load.go | 2 +- internal/chart/v3/loader/load_test.go | 3 ++- internal/chart/v3/util/save_test.go | 3 ++- internal/plugin/installer/extractor.go | 2 +- internal/plugin/installer/oci_installer.go | 3 ++- internal/plugin/sign.go | 2 +- pkg/chart/loader/archive/archive.go | 2 +- pkg/chart/loader/load.go | 6 +++--- pkg/chart/v2/lint/rules/crds.go | 2 +- pkg/chart/v2/lint/rules/template.go | 2 +- pkg/chart/v2/loader/archive.go | 4 ++-- pkg/chart/v2/loader/load.go | 2 +- pkg/chart/v2/loader/load_test.go | 3 ++- pkg/chart/v2/util/save_test.go | 3 ++- pkg/strvals/literal_parser.go | 5 +++-- pkg/strvals/parser.go | 2 +- 19 files changed, 29 insertions(+), 23 deletions(-) diff --git a/internal/chart/v3/lint/rules/crds.go b/internal/chart/v3/lint/rules/crds.go index 735573624..deedeb0f2 100644 --- a/internal/chart/v3/lint/rules/crds.go +++ b/internal/chart/v3/lint/rules/crds.go @@ -70,7 +70,7 @@ func Crds(linter *support.Linter) { var yamlStruct *k8sYamlStruct err := decoder.Decode(&yamlStruct) - if err == io.EOF { + if errors.Is(err, io.EOF) { break } diff --git a/internal/chart/v3/lint/rules/template.go b/internal/chart/v3/lint/rules/template.go index 204966364..38e602b7e 100644 --- a/internal/chart/v3/lint/rules/template.go +++ b/internal/chart/v3/lint/rules/template.go @@ -150,7 +150,7 @@ func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string var yamlStruct *k8sYamlStruct err := decoder.Decode(&yamlStruct) - if err == io.EOF { + if errors.Is(err, io.EOF) { break } diff --git a/internal/chart/v3/loader/archive.go b/internal/chart/v3/loader/archive.go index 358c2ce4d..a9d4faf8f 100644 --- a/internal/chart/v3/loader/archive.go +++ b/internal/chart/v3/loader/archive.go @@ -56,7 +56,7 @@ func LoadFile(name string) (*chart.Chart, error) { c, err := LoadArchive(raw) if err != nil { - if err == gzip.ErrHeader { + if errors.Is(err, gzip.ErrHeader) { return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %s)", name, err) } } diff --git a/internal/chart/v3/loader/load.go b/internal/chart/v3/loader/load.go index 9d657fb95..373c4659f 100644 --- a/internal/chart/v3/loader/load.go +++ b/internal/chart/v3/loader/load.go @@ -189,7 +189,7 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) { currentMap := map[string]interface{}{} raw, err := reader.Read() if err != nil { - if err == io.EOF { + if errors.Is(err, io.EOF) { break } return nil, fmt.Errorf("error reading yaml document: %w", err) diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index f91005bf6..12403f9c2 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -20,6 +20,7 @@ import ( "archive/tar" "bytes" "compress/gzip" + "errors" "io" "log" "os" @@ -116,7 +117,7 @@ func TestBomTestData(t *testing.T) { tr := tar.NewReader(unzipped) for { file, err := tr.Next() - if err == io.EOF { + if errors.Is(err, io.EOF) { break } if err != nil { diff --git a/internal/chart/v3/util/save_test.go b/internal/chart/v3/util/save_test.go index 62625919b..7a42a76af 100644 --- a/internal/chart/v3/util/save_test.go +++ b/internal/chart/v3/util/save_test.go @@ -21,6 +21,7 @@ import ( "bytes" "compress/gzip" "crypto/sha256" + "errors" "fmt" "io" "os" @@ -201,7 +202,7 @@ func retrieveAllHeadersFromTar(path string) ([]*tar.Header, error) { headers := []*tar.Header{} for { hd, err := tr.Next() - if err == io.EOF { + if errors.Is(err, io.EOF) { break } diff --git a/internal/plugin/installer/extractor.go b/internal/plugin/installer/extractor.go index 71efebc67..b753dfbca 100644 --- a/internal/plugin/installer/extractor.go +++ b/internal/plugin/installer/extractor.go @@ -140,7 +140,7 @@ func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error { tarReader := tar.NewReader(uncompressedStream) for { header, err := tarReader.Next() - if err == io.EOF { + if errors.Is(err, io.EOF) { break } if err != nil { diff --git a/internal/plugin/installer/oci_installer.go b/internal/plugin/installer/oci_installer.go index afbb42ca5..67f99b6f8 100644 --- a/internal/plugin/installer/oci_installer.go +++ b/internal/plugin/installer/oci_installer.go @@ -19,6 +19,7 @@ import ( "archive/tar" "bytes" "compress/gzip" + "errors" "fmt" "io" "log/slog" @@ -214,7 +215,7 @@ func extractTar(r io.Reader, targetDir string) error { for { header, err := tarReader.Next() - if err == io.EOF { + if errors.Is(err, io.EOF) { break } if err != nil { diff --git a/internal/plugin/sign.go b/internal/plugin/sign.go index 6b8aafd3e..6ddf113a2 100644 --- a/internal/plugin/sign.go +++ b/internal/plugin/sign.go @@ -63,7 +63,7 @@ func ExtractTgzPluginMetadata(r io.Reader) (*Metadata, error) { tr := tar.NewReader(gzr) for { header, err := tr.Next() - if err == io.EOF { + if errors.Is(err, io.EOF) { break } if err != nil { diff --git a/pkg/chart/loader/archive/archive.go b/pkg/chart/loader/archive/archive.go index c6875db3f..e98f5c333 100644 --- a/pkg/chart/loader/archive/archive.go +++ b/pkg/chart/loader/archive/archive.go @@ -68,7 +68,7 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { for { b := bytes.NewBuffer(nil) hd, err := tr.Next() - if err == io.EOF { + if errors.Is(err, io.EOF) { break } if err != nil { diff --git a/pkg/chart/loader/load.go b/pkg/chart/loader/load.go index dbc5d3004..3fd381825 100644 --- a/pkg/chart/loader/load.go +++ b/pkg/chart/loader/load.go @@ -131,8 +131,8 @@ func LoadFile(name string) (chart.Charter, error) { files, err := archive.LoadArchiveFiles(raw) if err != nil { - if err == gzip.ErrHeader { - return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %s)", name, err) + if errors.Is(err, gzip.ErrHeader) { + return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %w)", name, err) } return nil, errors.New("unable to load chart archive") } @@ -163,7 +163,7 @@ func LoadArchive(in io.Reader) (chart.Charter, error) { files, err := archive.LoadArchiveFiles(in) if err != nil { - if err == gzip.ErrHeader { + if errors.Is(err, gzip.ErrHeader) { return nil, fmt.Errorf("stream does not appear to be a valid chart file (details: %w)", err) } return nil, fmt.Errorf("unable to load chart archive: %w", err) diff --git a/pkg/chart/v2/lint/rules/crds.go b/pkg/chart/v2/lint/rules/crds.go index faef7dcf9..4bb4d370b 100644 --- a/pkg/chart/v2/lint/rules/crds.go +++ b/pkg/chart/v2/lint/rules/crds.go @@ -70,7 +70,7 @@ func Crds(linter *support.Linter) { var yamlStruct *k8sYamlStruct err := decoder.Decode(&yamlStruct) - if err == io.EOF { + if errors.Is(err, io.EOF) { break } diff --git a/pkg/chart/v2/lint/rules/template.go b/pkg/chart/v2/lint/rules/template.go index 0c633dc1a..43665aa3a 100644 --- a/pkg/chart/v2/lint/rules/template.go +++ b/pkg/chart/v2/lint/rules/template.go @@ -180,7 +180,7 @@ func (t *templateLinter) Lint() { var yamlStruct *k8sYamlStruct err := decoder.Decode(&yamlStruct) - if err == io.EOF { + if errors.Is(err, io.EOF) { break } diff --git a/pkg/chart/v2/loader/archive.go b/pkg/chart/v2/loader/archive.go index f6ed0e84f..c6885e125 100644 --- a/pkg/chart/v2/loader/archive.go +++ b/pkg/chart/v2/loader/archive.go @@ -56,8 +56,8 @@ func LoadFile(name string) (*chart.Chart, error) { c, err := LoadArchive(raw) if err != nil { - if err == gzip.ErrHeader { - return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %s)", name, err) + if errors.Is(err, gzip.ErrHeader) { + return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %w)", name, err) } } return c, err diff --git a/pkg/chart/v2/loader/load.go b/pkg/chart/v2/loader/load.go index 6fdaa4f7d..d466e247c 100644 --- a/pkg/chart/v2/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -216,7 +216,7 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) { currentMap := map[string]interface{}{} raw, err := reader.Read() if err != nil { - if err == io.EOF { + if errors.Is(err, io.EOF) { break } return nil, fmt.Errorf("error reading yaml document: %w", err) diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index ee0be5b18..397745dd6 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -20,6 +20,7 @@ import ( "archive/tar" "bytes" "compress/gzip" + "errors" "io" "log" "os" @@ -116,7 +117,7 @@ func TestBomTestData(t *testing.T) { tr := tar.NewReader(unzipped) for { file, err := tr.Next() - if err == io.EOF { + if errors.Is(err, io.EOF) { break } if err != nil { diff --git a/pkg/chart/v2/util/save_test.go b/pkg/chart/v2/util/save_test.go index e317d1c09..6d4e2c8cd 100644 --- a/pkg/chart/v2/util/save_test.go +++ b/pkg/chart/v2/util/save_test.go @@ -21,6 +21,7 @@ import ( "bytes" "compress/gzip" "crypto/sha256" + "errors" "fmt" "io" "os" @@ -205,7 +206,7 @@ func retrieveAllHeadersFromTar(path string) ([]*tar.Header, error) { headers := []*tar.Header{} for { hd, err := tr.Next() - if err == io.EOF { + if errors.Is(err, io.EOF) { break } diff --git a/pkg/strvals/literal_parser.go b/pkg/strvals/literal_parser.go index d34e5e854..d5d4c25b4 100644 --- a/pkg/strvals/literal_parser.go +++ b/pkg/strvals/literal_parser.go @@ -17,6 +17,7 @@ package strvals import ( "bytes" + "errors" "fmt" "io" "strconv" @@ -66,7 +67,7 @@ func (t *literalParser) parse() error { if err == nil { continue } - if err == io.EOF { + if errors.Is(err, io.EOF) { return nil } return err @@ -183,7 +184,7 @@ func (t *literalParser) listItem(list []interface{}, i, nestedNameLevel int) ([] case lastRune == '=': value, err := t.val() - if err != nil && err != io.EOF { + if err != nil && !errors.Is(err, io.EOF) { return list, err } return setIndex(list, i, string(value)) diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index 86e349f37..8eb761dce 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -161,7 +161,7 @@ func (t *parser) parse() error { if err == nil { continue } - if err == io.EOF { + if errors.Is(err, io.EOF) { return nil } return err From 592815ea2b020b354510685bfa61d252095baafb Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Sun, 14 Dec 2025 20:42:29 -0800 Subject: [PATCH 092/571] Fix `TestConcurrencyDownloadIndex` typo Signed-off-by: George Jenkins --- pkg/repo/v1/chartrepo_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/repo/v1/chartrepo_test.go b/pkg/repo/v1/chartrepo_test.go index a707cf36d..353ab62d6 100644 --- a/pkg/repo/v1/chartrepo_test.go +++ b/pkg/repo/v1/chartrepo_test.go @@ -94,7 +94,7 @@ func TestIndexCustomSchemeDownload(t *testing.T) { } } -func TestConcurrenyDownloadIndex(t *testing.T) { +func TestConcurrencyDownloadIndex(t *testing.T) { srv, err := startLocalServerForTests(nil) if err != nil { t.Fatal(err) From 364a7aa942201fcb10949dd4ad4b79c43a0cdaab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 05:07:55 +0000 Subject: [PATCH 093/571] chore(deps): bump github.com/fluxcd/cli-utils Bumps [github.com/fluxcd/cli-utils](https://github.com/fluxcd/cli-utils) from 0.36.0-flux.14 to 0.36.0-flux.15. - [Commits](https://github.com/fluxcd/cli-utils/compare/v0.36.0-flux.14...v0.36.0-flux.15) --- updated-dependencies: - dependency-name: github.com/fluxcd/cli-utils dependency-version: 0.36.0-flux.15 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 1dba4ea66..652b07919 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.18.0 - github.com/fluxcd/cli-utils v0.36.0-flux.14 + github.com/fluxcd/cli-utils v0.36.0-flux.15 github.com/foxcpp/go-mockdns v1.1.0 github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.13.0 @@ -120,7 +120,7 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/onsi/gomega v1.37.0 // indirect + github.com/onsi/gomega v1.38.2 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -172,7 +172,7 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/grpc v1.72.1 // indirect - google.golang.org/protobuf v1.36.6 // indirect + google.golang.org/protobuf v1.36.7 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index db38a021b..59796f68c 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/cli-utils v0.36.0-flux.14 h1:I//AMVUXTc+M04UtIXArMXQZCazGMwfemodV1j/yG8c= -github.com/fluxcd/cli-utils v0.36.0-flux.14/go.mod h1:uDo7BYOfbdmk/asnHuI0IQPl6u0FCgcN54AHDu3Y5As= +github.com/fluxcd/cli-utils v0.36.0-flux.15 h1:Et5QLnIpRjj+oZtM9gEybkAaoNsjysHq0y1253Ai94Y= +github.com/fluxcd/cli-utils v0.36.0-flux.15/go.mod h1:AqRUmWIfNE7cdL6NWSGF0bAlypGs+9x5UQ2qOtlEzv4= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -244,10 +244,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= -github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= -github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= -github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/onsi/ginkgo/v2 v2.25.2 h1:hepmgwx1D+llZleKQDMEvy8vIlCxMGt7W5ZxDjIEhsw= +github.com/onsi/ginkgo/v2 v2.25.2/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -496,8 +496,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From 5196b8431a40b9bd9e2e4aadb757b99bec11853f Mon Sep 17 00:00:00 2001 From: tison Date: Tue, 28 Oct 2025 17:37:22 +0800 Subject: [PATCH 094/571] Check plugin name is not used Signed-off-by: tison --- pkg/cmd/load_plugins.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/load_plugins.go b/pkg/cmd/load_plugins.go index ddda5fde6..d4bf7e7e3 100644 --- a/pkg/cmd/load_plugins.go +++ b/pkg/cmd/load_plugins.go @@ -132,7 +132,13 @@ func loadCLIPlugins(baseCmd *cobra.Command, out io.Writer) { DisableFlagParsing: true, } - // TODO: Make sure a command with this name does not already exist. + for _, cmd := range baseCmd.Commands() { + if cmd.Name() == c.Name() { + fmt.Fprintf(os.Stderr, "failed to load plugins: name conflicts %s\n", c.Name()) + return + } + } + baseCmd.AddCommand(c) // For completion, we try to load more details about the plugins so as to allow for command and From 3c6557dcf57ef4feee0d4840d4095f1479a63b40 Mon Sep 17 00:00:00 2001 From: tison Date: Thu, 30 Oct 2025 22:12:29 +0800 Subject: [PATCH 095/571] Fix TestCliPluginExitCode Signed-off-by: tison --- pkg/cmd/plugin_test.go | 10 +++++----- .../helm/plugins/{env => shortenv}/completion.yaml | 2 +- .../helm/plugins/{env => shortenv}/plugin-name.sh | 0 .../helm/plugins/{env => shortenv}/plugin.yaml | 2 +- pkg/cmd/testdata/output/plugin_list_comp.txt | 2 +- pkg/cmd/testdata/output/plugin_repeat_comp.txt | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) rename pkg/cmd/testdata/helmhome/helm/plugins/{env => shortenv}/completion.yaml (89%) rename pkg/cmd/testdata/helmhome/helm/plugins/{env => shortenv}/plugin-name.sh (100%) rename pkg/cmd/testdata/helmhome/helm/plugins/{env => shortenv}/plugin.yaml (93%) diff --git a/pkg/cmd/plugin_test.go b/pkg/cmd/plugin_test.go index 05cfe46f1..a250ba221 100644 --- a/pkg/cmd/plugin_test.go +++ b/pkg/cmd/plugin_test.go @@ -114,9 +114,9 @@ func TestLoadCLIPlugins(t *testing.T) { }{ {"args", "echo args", "This echos args", "-a -b -c\n", []string{"-a", "-b", "-c"}, 0}, {"echo", "echo stuff", "This echos stuff", "hello\n", []string{}, 0}, - {"env", "env stuff", "show the env", "HELM_PLUGIN_NAME=env\n", []string{}, 0}, {"exitwith", "exitwith code", "This exits with the specified exit code", "", []string{"2"}, 2}, {"fullenv", "show env vars", "show all env vars", fullEnvOutput, []string{}, 0}, + {"shortenv", "env stuff", "show the env", "HELM_PLUGIN_NAME=shortenv\n", []string{}, 0}, } pluginCmds := cmd.Commands() @@ -254,10 +254,6 @@ func TestLoadCLIPluginsForCompletion(t *testing.T) { tests := []staticCompletionDetails{ {"args", []string{}, []string{}, []staticCompletionDetails{}}, {"echo", []string{}, []string{}, []staticCompletionDetails{}}, - {"env", []string{}, []string{"global"}, []staticCompletionDetails{ - {"list", []string{}, []string{"a", "all", "log"}, []staticCompletionDetails{}}, - {"remove", []string{"all", "one"}, []string{}, []staticCompletionDetails{}}, - }}, {"exitwith", []string{}, []string{}, []staticCompletionDetails{ {"code", []string{}, []string{"a", "b"}, []staticCompletionDetails{}}, }}, @@ -268,6 +264,10 @@ func TestLoadCLIPluginsForCompletion(t *testing.T) { {"more", []string{"one", "two"}, []string{"b", "ball"}, []staticCompletionDetails{}}, }}, }}, + {"shortenv", []string{}, []string{"global"}, []staticCompletionDetails{ + {"list", []string{}, []string{"a", "all", "log"}, []staticCompletionDetails{}}, + {"remove", []string{"all", "one"}, []string{}, []staticCompletionDetails{}}, + }}, } checkCommand(t, cmd.Commands(), tests) } diff --git a/pkg/cmd/testdata/helmhome/helm/plugins/env/completion.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/shortenv/completion.yaml similarity index 89% rename from pkg/cmd/testdata/helmhome/helm/plugins/env/completion.yaml rename to pkg/cmd/testdata/helmhome/helm/plugins/shortenv/completion.yaml index e479a0503..027573ed4 100644 --- a/pkg/cmd/testdata/helmhome/helm/plugins/env/completion.yaml +++ b/pkg/cmd/testdata/helmhome/helm/plugins/shortenv/completion.yaml @@ -1,4 +1,4 @@ -name: env +name: shortenv commands: - name: list flags: diff --git a/pkg/cmd/testdata/helmhome/helm/plugins/env/plugin-name.sh b/pkg/cmd/testdata/helmhome/helm/plugins/shortenv/plugin-name.sh similarity index 100% rename from pkg/cmd/testdata/helmhome/helm/plugins/env/plugin-name.sh rename to pkg/cmd/testdata/helmhome/helm/plugins/shortenv/plugin-name.sh diff --git a/pkg/cmd/testdata/helmhome/helm/plugins/env/plugin.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/shortenv/plugin.yaml similarity index 93% rename from pkg/cmd/testdata/helmhome/helm/plugins/env/plugin.yaml rename to pkg/cmd/testdata/helmhome/helm/plugins/shortenv/plugin.yaml index 78a0a23fb..5fe053ed0 100644 --- a/pkg/cmd/testdata/helmhome/helm/plugins/env/plugin.yaml +++ b/pkg/cmd/testdata/helmhome/helm/plugins/shortenv/plugin.yaml @@ -1,6 +1,6 @@ --- apiVersion: v1 -name: env +name: shortenv type: cli/v1 runtime: subprocess config: diff --git a/pkg/cmd/testdata/output/plugin_list_comp.txt b/pkg/cmd/testdata/output/plugin_list_comp.txt index 833efc5e9..1dff43551 100644 --- a/pkg/cmd/testdata/output/plugin_list_comp.txt +++ b/pkg/cmd/testdata/output/plugin_list_comp.txt @@ -1,7 +1,7 @@ args echo args echo echo stuff -env env stuff exitwith exitwith code fullenv show env vars +shortenv env stuff :4 Completion ended with directive: ShellCompDirectiveNoFileComp diff --git a/pkg/cmd/testdata/output/plugin_repeat_comp.txt b/pkg/cmd/testdata/output/plugin_repeat_comp.txt index 3fa05f0b3..b46c1b7d4 100644 --- a/pkg/cmd/testdata/output/plugin_repeat_comp.txt +++ b/pkg/cmd/testdata/output/plugin_repeat_comp.txt @@ -1,6 +1,6 @@ echo echo stuff -env env stuff exitwith exitwith code fullenv show env vars +shortenv env stuff :4 Completion ended with directive: ShellCompDirectiveNoFileComp From 9772037ed8a9e273b8c125fbf39bc7254ac53b05 Mon Sep 17 00:00:00 2001 From: tison Date: Mon, 15 Dec 2025 13:37:55 +0800 Subject: [PATCH 096/571] Update to use slog Signed-off-by: tison --- pkg/cmd/load_plugins.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/load_plugins.go b/pkg/cmd/load_plugins.go index d4bf7e7e3..7bdbf597d 100644 --- a/pkg/cmd/load_plugins.go +++ b/pkg/cmd/load_plugins.go @@ -134,7 +134,7 @@ func loadCLIPlugins(baseCmd *cobra.Command, out io.Writer) { for _, cmd := range baseCmd.Commands() { if cmd.Name() == c.Name() { - fmt.Fprintf(os.Stderr, "failed to load plugins: name conflicts %s\n", c.Name()) + slog.Error("failed to load plugins: name conflicts", slog.String("name", c.Name())) return } } From f92ae18977bfe4fad74c7cb0e8c7f7fc68d85306 Mon Sep 17 00:00:00 2001 From: Deepak Date: Tue, 16 Dec 2025 11:35:11 +0530 Subject: [PATCH 097/571] fix(doc): Update default wait strategy fixes: #31652 Default wait strategy is hookOnly, so updated the docs to reflect the same Signed-off-by: Deepak Chethan --- pkg/cmd/flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index 939523ffd..02981420e 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -59,7 +59,7 @@ func AddWaitFlag(cmd *cobra.Command, wait *kube.WaitStrategy) { cmd.Flags().Var( newWaitValue(kube.HookOnlyStrategy, wait), "wait", - "if specified, wait until resources are ready (up to --timeout). Values: 'watcher' (default), 'hookOnly', and 'legacy'.", + "if specified, wait until resources are ready (up to --timeout). Values: 'hookOnly' (default), 'watcher', and 'legacy'.", ) // Sets the strategy to use the watcher strategy if `--wait` is used without an argument cmd.Flags().Lookup("wait").NoOptDefVal = string(kube.StatusWatcherStrategy) From 3dd54ed0b693e5e5805cceb535ed5167fa5ada25 Mon Sep 17 00:00:00 2001 From: Mohsen Mottaghi Date: Fri, 5 Dec 2025 17:14:07 +0330 Subject: [PATCH 098/571] fix: use namespace-scoped watching to avoid cluster-wide LIST permissions Signed-off-by: Mohsen Mottaghi --- pkg/kube/statuswait.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index 6077af165..a518f0c04 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -132,7 +132,9 @@ func (w *statusWaiter) waitForDelete(ctx context.Context, resourceList ResourceL } resources = append(resources, obj) } - eventCh := sw.Watch(cancelCtx, resources, watcher.Options{}) + eventCh := sw.Watch(cancelCtx, resources, watcher.Options{ + RESTScopeStrategy: watcher.RESTScopeNamespace, + }) statusCollector := collector.NewResourceStatusCollector(resources) done := statusCollector.ListenWithObserver(eventCh, statusObserver(cancel, status.NotFoundStatus)) <-done @@ -175,7 +177,9 @@ func (w *statusWaiter) wait(ctx context.Context, resourceList ResourceList, sw w resources = append(resources, obj) } - eventCh := sw.Watch(cancelCtx, resources, watcher.Options{}) + eventCh := sw.Watch(cancelCtx, resources, watcher.Options{ + RESTScopeStrategy: watcher.RESTScopeNamespace, + }) statusCollector := collector.NewResourceStatusCollector(resources) done := statusCollector.ListenWithObserver(eventCh, statusObserver(cancel, status.CurrentStatus)) <-done From dd44f4ecd0168a074bc75a43d78d6128c95f1d81 Mon Sep 17 00:00:00 2001 From: Mohsen Mottaghi Date: Mon, 8 Dec 2025 22:59:46 +0330 Subject: [PATCH 099/571] test(statuswait): add some tests for statuswait Adding some tests for multi namespace deployment, simulate restrcited rbac access and mixed namespace scope and cluster scope resources Signed-off-by: Mohsen Mottaghi --- pkg/kube/statuswait_test.go | 463 ++++++++++++++++++++++++++++++++++++ 1 file changed, 463 insertions(+) diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index 4b06da896..9baa0017f 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -17,7 +17,10 @@ limitations under the License. package kube // import "helm.sh/helm/v3/pkg/kube" import ( + "context" "errors" + "fmt" + "strings" "testing" "time" @@ -27,11 +30,14 @@ import ( appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/client-go/dynamic" dynamicfake "k8s.io/client-go/dynamic/fake" "k8s.io/kubectl/pkg/scheme" ) @@ -153,6 +159,83 @@ spec: - containerPort: 80 ` +var podNamespace1Manifest = ` +apiVersion: v1 +kind: Pod +metadata: + name: pod-ns1 + namespace: namespace-1 +status: + conditions: + - type: Ready + status: "True" + phase: Running +` + +var podNamespace2Manifest = ` +apiVersion: v1 +kind: Pod +metadata: + name: pod-ns2 + namespace: namespace-2 +status: + conditions: + - type: Ready + status: "True" + phase: Running +` + +var podNamespace1NoStatusManifest = ` +apiVersion: v1 +kind: Pod +metadata: + name: pod-ns1 + namespace: namespace-1 +` + +var jobNamespace1CompleteManifest = ` +apiVersion: batch/v1 +kind: Job +metadata: + name: job-ns1 + namespace: namespace-1 + generation: 1 +status: + succeeded: 1 + active: 0 + conditions: + - type: Complete + status: "True" +` + +var podNamespace2SucceededManifest = ` +apiVersion: v1 +kind: Pod +metadata: + name: pod-ns2 + namespace: namespace-2 +status: + phase: Succeeded +` + +var clusterRoleManifest = ` +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: test-cluster-role +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list"] +` + +var namespaceManifest = ` +apiVersion: v1 +kind: Namespace +metadata: + name: test-namespace +` + func getGVR(t *testing.T, mapper meta.RESTMapper, obj *unstructured.Unstructured) schema.GroupVersionResource { t.Helper() gvk := obj.GroupVersionKind() @@ -448,3 +531,383 @@ func TestWatchForReady(t *testing.T) { }) } } + +func TestStatusWaitMultipleNamespaces(t *testing.T) { + t.Parallel() + tests := []struct { + name string + objManifests []string + expectErrs []error + testFunc func(statusWaiter, ResourceList, time.Duration) error + }{ + { + name: "pods in multiple namespaces", + objManifests: []string{podNamespace1Manifest, podNamespace2Manifest}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.Wait(rl, timeout) + }, + }, + { + name: "hooks in multiple namespaces", + objManifests: []string{jobNamespace1CompleteManifest, podNamespace2SucceededManifest}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.WatchUntilReady(rl, timeout) + }, + }, + { + name: "error when resource not ready in one namespace", + objManifests: []string{podNamespace1NoStatusManifest, podNamespace2Manifest}, + expectErrs: []error{errors.New("resource not ready, name: pod-ns1, kind: Pod, status: InProgress"), errors.New("context deadline exceeded")}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.Wait(rl, timeout) + }, + }, + { + name: "delete resources in multiple namespaces", + objManifests: []string{podNamespace1Manifest, podNamespace2Manifest}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.WaitForDelete(rl, timeout) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + batchv1.SchemeGroupVersion.WithKind("Job"), + ) + sw := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + } + objs := getRuntimeObjFromManifests(t, tt.objManifests) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + assert.NoError(t, err) + } + + if strings.Contains(tt.name, "delete") { + timeUntilDelete := time.Millisecond * 500 + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + go func() { + time.Sleep(timeUntilDelete) + err := fakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName()) + assert.NoError(t, err) + }() + } + } + + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + err := tt.testFunc(sw, resourceList, time.Second*3) + if tt.expectErrs != nil { + assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) + return + } + assert.NoError(t, err) + }) + } +} + +type restrictedDynamicClient struct { + dynamic.Interface + allowedNamespaces map[string]bool + clusterScopedListAttempted bool +} + +func newRestrictedDynamicClient(baseClient dynamic.Interface, allowedNamespaces []string) *restrictedDynamicClient { + allowed := make(map[string]bool) + for _, ns := range allowedNamespaces { + allowed[ns] = true + } + return &restrictedDynamicClient{ + Interface: baseClient, + allowedNamespaces: allowed, + } +} + +func (r *restrictedDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface { + return &restrictedNamespaceableResource{ + NamespaceableResourceInterface: r.Interface.Resource(resource), + allowedNamespaces: r.allowedNamespaces, + clusterScopedListAttempted: &r.clusterScopedListAttempted, + } +} + +type restrictedNamespaceableResource struct { + dynamic.NamespaceableResourceInterface + allowedNamespaces map[string]bool + clusterScopedListAttempted *bool +} + +func (r *restrictedNamespaceableResource) Namespace(ns string) dynamic.ResourceInterface { + return &restrictedResource{ + ResourceInterface: r.NamespaceableResourceInterface.Namespace(ns), + namespace: ns, + allowedNamespaces: r.allowedNamespaces, + clusterScopedListAttempted: r.clusterScopedListAttempted, + } +} + +func (r *restrictedNamespaceableResource) List(_ context.Context, _ metav1.ListOptions) (*unstructured.UnstructuredList, error) { + *r.clusterScopedListAttempted = true + return nil, apierrors.NewForbidden( + schema.GroupResource{Resource: "pods"}, + "", + fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources"), + ) +} + +type restrictedResource struct { + dynamic.ResourceInterface + namespace string + allowedNamespaces map[string]bool + clusterScopedListAttempted *bool +} + +func (r *restrictedResource) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) { + if r.namespace == "" { + *r.clusterScopedListAttempted = true + return nil, apierrors.NewForbidden( + schema.GroupResource{Resource: "pods"}, + "", + fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources"), + ) + } + if !r.allowedNamespaces[r.namespace] { + return nil, apierrors.NewForbidden( + schema.GroupResource{Resource: "pods"}, + "", + fmt.Errorf("user does not have LIST permissions in namespace %q", r.namespace), + ) + } + return r.ResourceInterface.List(ctx, opts) +} + +func TestStatusWaitRestrictedRBAC(t *testing.T) { + t.Parallel() + tests := []struct { + name string + objManifests []string + allowedNamespaces []string + expectErrs []error + testFunc func(statusWaiter, ResourceList, time.Duration) error + }{ + { + name: "pods in multiple namespaces with namespace permissions", + objManifests: []string{podNamespace1Manifest, podNamespace2Manifest}, + allowedNamespaces: []string{"namespace-1", "namespace-2"}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.Wait(rl, timeout) + }, + }, + { + name: "delete pods in multiple namespaces with namespace permissions", + objManifests: []string{podNamespace1Manifest, podNamespace2Manifest}, + allowedNamespaces: []string{"namespace-1", "namespace-2"}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.WaitForDelete(rl, timeout) + }, + }, + { + name: "hooks in multiple namespaces with namespace permissions", + objManifests: []string{jobNamespace1CompleteManifest, podNamespace2SucceededManifest}, + allowedNamespaces: []string{"namespace-1", "namespace-2"}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.WatchUntilReady(rl, timeout) + }, + }, + { + name: "error when cluster-scoped resource included", + objManifests: []string{podNamespace1Manifest, clusterRoleManifest}, + allowedNamespaces: []string{"namespace-1"}, + expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.Wait(rl, timeout) + }, + }, + { + name: "error when deleting cluster-scoped resource", + objManifests: []string{podNamespace1Manifest, namespaceManifest}, + allowedNamespaces: []string{"namespace-1"}, + expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.WaitForDelete(rl, timeout) + }, + }, + { + name: "error when accessing disallowed namespace", + objManifests: []string{podNamespace1Manifest, podNamespace2Manifest}, + allowedNamespaces: []string{"namespace-1"}, + expectErrs: []error{fmt.Errorf("user does not have LIST permissions in namespace %q", "namespace-2")}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.Wait(rl, timeout) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + baseFakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + batchv1.SchemeGroupVersion.WithKind("Job"), + schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1"}.WithKind("ClusterRole"), + v1.SchemeGroupVersion.WithKind("Namespace"), + ) + restrictedClient := newRestrictedDynamicClient(baseFakeClient, tt.allowedNamespaces) + sw := statusWaiter{ + client: restrictedClient, + restMapper: fakeMapper, + } + objs := getRuntimeObjFromManifests(t, tt.objManifests) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := baseFakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + assert.NoError(t, err) + } + + if strings.Contains(tt.name, "delet") { + timeUntilDelete := time.Millisecond * 500 + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + go func() { + time.Sleep(timeUntilDelete) + err := baseFakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName()) + assert.NoError(t, err) + }() + } + } + + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + err := tt.testFunc(sw, resourceList, time.Second*3) + if tt.expectErrs != nil { + require.Error(t, err) + for _, expectedErr := range tt.expectErrs { + assert.Contains(t, err.Error(), expectedErr.Error()) + } + return + } + assert.NoError(t, err) + assert.False(t, restrictedClient.clusterScopedListAttempted) + }) + } +} + +func TestStatusWaitMixedResources(t *testing.T) { + t.Parallel() + tests := []struct { + name string + objManifests []string + allowedNamespaces []string + expectErrs []error + testFunc func(statusWaiter, ResourceList, time.Duration) error + }{ + { + name: "wait succeeds with namespace-scoped resources only", + objManifests: []string{podNamespace1Manifest, podNamespace2Manifest}, + allowedNamespaces: []string{"namespace-1", "namespace-2"}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.Wait(rl, timeout) + }, + }, + { + name: "wait fails when cluster-scoped resource included", + objManifests: []string{podNamespace1Manifest, clusterRoleManifest}, + allowedNamespaces: []string{"namespace-1"}, + expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.Wait(rl, timeout) + }, + }, + { + name: "waitForDelete fails when cluster-scoped resource included", + objManifests: []string{podNamespace1Manifest, clusterRoleManifest}, + allowedNamespaces: []string{"namespace-1"}, + expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.WaitForDelete(rl, timeout) + }, + }, + { + name: "wait fails when namespace resource included", + objManifests: []string{podNamespace1Manifest, namespaceManifest}, + allowedNamespaces: []string{"namespace-1"}, + expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.Wait(rl, timeout) + }, + }, + { + name: "error when accessing disallowed namespace", + objManifests: []string{podNamespace1Manifest, podNamespace2Manifest}, + allowedNamespaces: []string{"namespace-1"}, + expectErrs: []error{fmt.Errorf("user does not have LIST permissions in namespace %q", "namespace-2")}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.Wait(rl, timeout) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + baseFakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + batchv1.SchemeGroupVersion.WithKind("Job"), + schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1"}.WithKind("ClusterRole"), + v1.SchemeGroupVersion.WithKind("Namespace"), + ) + restrictedClient := newRestrictedDynamicClient(baseFakeClient, tt.allowedNamespaces) + sw := statusWaiter{ + client: restrictedClient, + restMapper: fakeMapper, + } + objs := getRuntimeObjFromManifests(t, tt.objManifests) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := baseFakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + assert.NoError(t, err) + } + + if strings.Contains(tt.name, "delet") { + timeUntilDelete := time.Millisecond * 500 + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + go func() { + time.Sleep(timeUntilDelete) + err := baseFakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName()) + assert.NoError(t, err) + }() + } + } + + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + err := tt.testFunc(sw, resourceList, time.Second*3) + if tt.expectErrs != nil { + require.Error(t, err) + for _, expectedErr := range tt.expectErrs { + assert.Contains(t, err.Error(), expectedErr.Error()) + } + return + } + assert.NoError(t, err) + assert.False(t, restrictedClient.clusterScopedListAttempted) + }) + } +} From a1543d5947640b0ad4ac5c671273a28aca15e56e Mon Sep 17 00:00:00 2001 From: Mohsen Mottaghi Date: Tue, 16 Dec 2025 17:33:29 +0330 Subject: [PATCH 100/571] test(statuswait): add more tests suggested by Copilot code review add missing coverage for the positive case where cluster-scoped resources (like ClusterRole or Namespace) should work correctly Signed-off-by: Mohsen Mottaghi --- pkg/kube/statuswait_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index 9baa0017f..cd45f1e19 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -569,6 +569,34 @@ func TestStatusWaitMultipleNamespaces(t *testing.T) { return sw.WaitForDelete(rl, timeout) }, }, + { + name: "cluster-scoped resources work correctly with unrestricted permissions", + objManifests: []string{podNamespace1Manifest, clusterRoleManifest}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.Wait(rl, timeout) + }, + }, + { + name: "namespace-scoped and cluster-scoped resources work together", + objManifests: []string{podNamespace1Manifest, podNamespace2Manifest, clusterRoleManifest}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.Wait(rl, timeout) + }, + }, + { + name: "delete cluster-scoped resources works correctly", + objManifests: []string{podNamespace1Manifest, namespaceManifest}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.WaitForDelete(rl, timeout) + }, + }, + { + name: "watch cluster-scoped resources works correctly", + objManifests: []string{clusterRoleManifest}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.WatchUntilReady(rl, timeout) + }, + }, } for _, tt := range tests { @@ -579,6 +607,8 @@ func TestStatusWaitMultipleNamespaces(t *testing.T) { fakeMapper := testutil.NewFakeRESTMapper( v1.SchemeGroupVersion.WithKind("Pod"), batchv1.SchemeGroupVersion.WithKind("Job"), + schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1"}.WithKind("ClusterRole"), + v1.SchemeGroupVersion.WithKind("Namespace"), ) sw := statusWaiter{ client: fakeClient, From d6b35cedeb0519b648941911298ebb08fa3b0edf Mon Sep 17 00:00:00 2001 From: Mohsen Mottaghi Date: Tue, 16 Dec 2025 17:43:15 +0330 Subject: [PATCH 101/571] test(statuswait): fix Copilot code review suggestion for goroutine in tests Signed-off-by: Mohsen Mottaghi --- pkg/kube/statuswait_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index cd45f1e19..4e31ce31c 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -315,11 +315,11 @@ func TestStatusWaitForDelete(t *testing.T) { for _, objToDelete := range objsToDelete { u := objToDelete.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - go func() { + go func(gvr schema.GroupVersionResource, u *unstructured.Unstructured) { time.Sleep(timeUntilPodDelete) err := fakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName()) assert.NoError(t, err) - }() + }(gvr, u) } resourceList := getResourceListFromRuntimeObjs(t, c, objsToCreate) err := statusWaiter.WaitForDelete(resourceList, timeout) @@ -627,11 +627,11 @@ func TestStatusWaitMultipleNamespaces(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - go func() { + go func(gvr schema.GroupVersionResource, u *unstructured.Unstructured) { time.Sleep(timeUntilDelete) err := fakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName()) assert.NoError(t, err) - }() + }(gvr, u) } } @@ -812,11 +812,11 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - go func() { + go func(gvr schema.GroupVersionResource, u *unstructured.Unstructured) { time.Sleep(timeUntilDelete) err := baseFakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName()) assert.NoError(t, err) - }() + }(gvr, u) } } @@ -919,11 +919,11 @@ func TestStatusWaitMixedResources(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - go func() { + go func(gvr schema.GroupVersionResource, u *unstructured.Unstructured) { time.Sleep(timeUntilDelete) err := baseFakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName()) assert.NoError(t, err) - }() + }(gvr, u) } } From 55a4aed67f17b51c7ff80ef05529d82255e9888d Mon Sep 17 00:00:00 2001 From: Deepak Chethan Date: Wed, 17 Dec 2025 14:41:37 +0530 Subject: [PATCH 102/571] doc: update based on review suggestion Co-authored-by: Terry Howe Signed-off-by: Deepak Chethan --- pkg/cmd/flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index 02981420e..251bfa032 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -59,7 +59,7 @@ func AddWaitFlag(cmd *cobra.Command, wait *kube.WaitStrategy) { cmd.Flags().Var( newWaitValue(kube.HookOnlyStrategy, wait), "wait", - "if specified, wait until resources are ready (up to --timeout). Values: 'hookOnly' (default), 'watcher', and 'legacy'.", + "if specified, wait until resources are ready (up to --timeout). Values: 'watcher', 'hookOnly', and 'legacy'.", ) // Sets the strategy to use the watcher strategy if `--wait` is used without an argument cmd.Flags().Lookup("wait").NoOptDefVal = string(kube.StatusWatcherStrategy) From 12e8b715aa0732b613c3a9896fa6af29b3201536 Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Thu, 18 Dec 2025 00:14:08 +0530 Subject: [PATCH 103/571] fix: doc string added Signed-off-by: Mujib Ahasan --- pkg/action/validate.go | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/pkg/action/validate.go b/pkg/action/validate.go index 045a70955..8e58af7f0 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -46,8 +46,8 @@ func requireAdoption(resources kube.ResourceList) (kube.ResourceList, error) { return err } - skip, err := validateNameAndgenerateName(info) - if skip { + isGenerateName, err := validateNameAndgenerateName(info) + if isGenerateName { return err } @@ -75,8 +75,8 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN return err } - skip, err := validateNameAndgenerateName(info) - if skip { + isGenerateName, err := validateNameAndgenerateName(info) + if isGenerateName { return err } @@ -210,11 +210,20 @@ func mergeStrStrMaps(current, desired map[string]string) map[string]string { return result } +// validateNameAndgenerateName validates that an object only has either `Name` or `GenerateName` set (and not both) +// If `GenerateName` is set, true is returned +// If an invalid combination of `Name` and `GenerateName` are set, an error is returned func validateNameAndgenerateName(info *resource.Info) (bool, error) { - accessor, _ := meta.Accessor(info.Object) + accessor, err := meta.Accessor(info.Object) + if err != nil { + return true, err + } + if info.Name == "" && accessor.GetGenerateName() != "" { return true, nil - } else if info.Name != "" && accessor.GetGenerateName() != "" { + } + + if info.Name != "" && accessor.GetGenerateName() != "" { return true, fmt.Errorf("metadata.name and metadata.generateName cannot both be set") } From 91231430a8fefa8b57bf9810a8001351122a5b1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Dec 2025 21:12:53 +0000 Subject: [PATCH 104/571] chore(deps): bump github.com/BurntSushi/toml from 1.5.0 to 1.6.0 Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 1.5.0 to 1.6.0. - [Release notes](https://github.com/BurntSushi/toml/releases) - [Commits](https://github.com/BurntSushi/toml/compare/v1.5.0...v1.6.0) --- updated-dependencies: - dependency-name: github.com/BurntSushi/toml dependency-version: 1.6.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 652b07919..280b40e45 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25.0 require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 - github.com/BurntSushi/toml v1.5.0 + github.com/BurntSushi/toml v1.6.0 github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/Masterminds/semver/v3 v3.4.0 github.com/Masterminds/sprig/v3 v3.3.0 diff --git a/go.sum b/go.sum index 59796f68c..2681870c4 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= -github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= From 97cde794c68eec98240125f7743296a62d92c481 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 21:11:13 +0000 Subject: [PATCH 105/571] chore(deps): bump github.com/tetratelabs/wazero from 1.10.1 to 1.11.0 Bumps [github.com/tetratelabs/wazero](https://github.com/tetratelabs/wazero) from 1.10.1 to 1.11.0. - [Release notes](https://github.com/tetratelabs/wazero/releases) - [Commits](https://github.com/tetratelabs/wazero/compare/v1.10.1...v1.11.0) --- updated-dependencies: - dependency-name: github.com/tetratelabs/wazero dependency-version: 1.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 652b07919..4541abe3b 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 - github.com/tetratelabs/wazero v1.10.1 + github.com/tetratelabs/wazero v1.11.0 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.46.0 golang.org/x/term v0.38.0 diff --git a/go.sum b/go.sum index 59796f68c..5a63359b2 100644 --- a/go.sum +++ b/go.sum @@ -321,8 +321,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q= github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk= -github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8= -github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU= +github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA= +github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= From c6d9a5bdc21efcc9c186ebc533436c31ce4d09c4 Mon Sep 17 00:00:00 2001 From: Branch Vincent Date: Fri, 12 Jul 2024 17:52:07 -0700 Subject: [PATCH 106/571] build: set kube version via `debug.BuildInfo` Signed-off-by: Branch Vincent Signed-off-by: George Jenkins --- Makefile | 14 ---- internal/chart/v3/lint/lint_test.go | 10 --- internal/chart/v3/lint/rules/deprecations.go | 36 ++++------ internal/version/clientgo.go | 44 ++++++++++++ internal/version/clientgo_test.go | 30 +++++++++ internal/version/version.go | 42 ++++++++++-- pkg/chart/common/capabilities.go | 71 +++++++++++++++----- pkg/chart/common/capabilities_test.go | 8 +-- pkg/chart/v2/lint/lint_test.go | 10 --- pkg/chart/v2/lint/rules/deprecations.go | 24 ++----- pkg/cmd/lint_test.go | 2 +- pkg/cmd/testdata/output/version.txt | 2 +- 12 files changed, 187 insertions(+), 106 deletions(-) create mode 100644 internal/version/clientgo.go create mode 100644 internal/version/clientgo_test.go diff --git a/Makefile b/Makefile index 25b8a56f6..a18b83f0d 100644 --- a/Makefile +++ b/Makefile @@ -58,20 +58,6 @@ LDFLAGS += -X helm.sh/helm/v4/internal/version.gitCommit=${GIT_COMMIT} LDFLAGS += -X helm.sh/helm/v4/internal/version.gitTreeState=${GIT_DIRTY} LDFLAGS += $(EXT_LDFLAGS) -# Define constants based on the client-go version -K8S_MODULES_VER=$(subst ., ,$(subst v,,$(shell go list -f '{{.Version}}' -m k8s.io/client-go))) -K8S_MODULES_MAJOR_VER=$(shell echo $$(($(firstword $(K8S_MODULES_VER)) + 1))) -K8S_MODULES_MINOR_VER=$(word 2,$(K8S_MODULES_VER)) - -LDFLAGS += -X helm.sh/helm/v4/pkg/chart/v2/lint/rules.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER) -LDFLAGS += -X helm.sh/helm/v4/pkg/chart/v2/lint/rules.k8sVersionMinor=$(K8S_MODULES_MINOR_VER) -LDFLAGS += -X helm.sh/helm/v4/pkg/internal/v3/lint/rules.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER) -LDFLAGS += -X helm.sh/helm/v4/pkg/internal/v3/lint/rules.k8sVersionMinor=$(K8S_MODULES_MINOR_VER) -LDFLAGS += -X helm.sh/helm/v4/pkg/chart/common.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER) -LDFLAGS += -X helm.sh/helm/v4/pkg/chart/common.k8sVersionMinor=$(K8S_MODULES_MINOR_VER) -LDFLAGS += -X helm.sh/helm/v4/internal/version.kubeClientVersionMajor=$(K8S_MODULES_MAJOR_VER) -LDFLAGS += -X helm.sh/helm/v4/internal/version.kubeClientVersionMinor=$(K8S_MODULES_MINOR_VER) - .PHONY: all all: build diff --git a/internal/chart/v3/lint/lint_test.go b/internal/chart/v3/lint/lint_test.go index d61a9a740..221de8572 100644 --- a/internal/chart/v3/lint/lint_test.go +++ b/internal/chart/v3/lint/lint_test.go @@ -175,16 +175,6 @@ func TestHelmCreateChart(t *testing.T) { // // Resources like hpa and ingress, which are disabled by default in values.yaml are enabled here using the equivalent // of the `--set` flag. -// -// Note: This test requires the following ldflags to be set per the current Kubernetes version to avoid false-positive -// results. -// 1. -X helm.sh/helm/v4/pkg/lint/rules.k8sVersionMajor= -// 2. -X helm.sh/helm/v4/pkg/lint/rules.k8sVersionMinor= -// or directly use '$(LDFLAGS)' in Makefile. -// -// When run without ldflags, the test passes giving a false-positive result. This is because the variables -// `k8sVersionMajor` and `k8sVersionMinor` by default are set to an older version of Kubernetes, with which, there -// might not be the deprecation warning. func TestHelmCreateChart_CheckDeprecatedWarnings(t *testing.T) { createdChart, err := chartutil.Create("checkdeprecatedwarnings", t.TempDir()) if err != nil { diff --git a/internal/chart/v3/lint/rules/deprecations.go b/internal/chart/v3/lint/rules/deprecations.go index b088dda2b..a607a5fb4 100644 --- a/internal/chart/v3/lint/rules/deprecations.go +++ b/internal/chart/v3/lint/rules/deprecations.go @@ -28,15 +28,7 @@ import ( kscheme "k8s.io/client-go/kubernetes/scheme" ) -var ( - // This should be set in the Makefile based on the version of client-go being imported. - // These constants will be overwritten with LDFLAGS. The version components must be - // strings in order for LDFLAGS to set them. - k8sVersionMajor = "1" - k8sVersionMinor = "20" -) - -// deprecatedAPIError indicates that an API is deprecated in Kubernetes +// deprecatedAPIError indicates than an API is deprecated in Kubernetes type deprecatedAPIError struct { Deprecated string Message string @@ -56,33 +48,29 @@ func validateNoDeprecations(resource *k8sYamlStruct, kubeVersion *common.KubeVer return nil } - majorVersion := k8sVersionMajor - minorVersion := k8sVersionMinor - - if kubeVersion != nil { - majorVersion = kubeVersion.Major - minorVersion = kubeVersion.Minor + if kubeVersion == nil { + kubeVersion = &common.DefaultCapabilities.KubeVersion } - runtimeObject, err := resourceToRuntimeObject(resource) + kubeVersionMajor, err := strconv.Atoi(kubeVersion.Major) if err != nil { - // do not error for non-kubernetes resources - if runtime.IsNotRegisteredError(err) { - return nil - } return err } - - major, err := strconv.Atoi(majorVersion) + kubeVersionMinor, err := strconv.Atoi(kubeVersion.Minor) if err != nil { return err } - minor, err := strconv.Atoi(minorVersion) + + runtimeObject, err := resourceToRuntimeObject(resource) if err != nil { + // do not error for non-kubernetes resources + if runtime.IsNotRegisteredError(err) { + return nil + } return err } - if !deprecation.IsDeprecated(runtimeObject, major, minor) { + if !deprecation.IsDeprecated(runtimeObject, kubeVersionMajor, kubeVersionMinor) { return nil } gvk := fmt.Sprintf("%s %s", resource.APIVersion, resource.Kind) diff --git a/internal/version/clientgo.go b/internal/version/clientgo.go new file mode 100644 index 000000000..ab2a38fd5 --- /dev/null +++ b/internal/version/clientgo.go @@ -0,0 +1,44 @@ +/* +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 version + +import ( + "fmt" + "runtime/debug" + "slices" + + _ "k8s.io/client-go/kubernetes" // Force k8s.io/client-go to be included in the build +) + +func K8sIOClientGoModVersion() (string, error) { + info, ok := debug.ReadBuildInfo() + if !ok { + return "", fmt.Errorf("failed to read build info") + } + + idx := slices.IndexFunc(info.Deps, func(m *debug.Module) bool { + return m.Path == "k8s.io/client-go" + }) + + if idx == -1 { + return "", fmt.Errorf("k8s.io/client-go not found in build info") + } + + m := info.Deps[idx] + + return m.Version, nil +} diff --git a/internal/version/clientgo_test.go b/internal/version/clientgo_test.go new file mode 100644 index 000000000..624c669af --- /dev/null +++ b/internal/version/clientgo_test.go @@ -0,0 +1,30 @@ +/* +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 version + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestK8sClientGoModVersion(t *testing.T) { + // Unfortunately, test builds don't include debug info / module info + // So we expect "K8sIOClientGoModVersion" to return error + _, err := K8sIOClientGoModVersion() + require.ErrorContains(t, err, "k8s.io/client-go not found in build info") +} diff --git a/internal/version/version.go b/internal/version/version.go index f9e76eab7..3daf80893 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -14,13 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package version // import "helm.sh/helm/v4/internal/version" +package version import ( "flag" "fmt" + "log/slog" "runtime" "strings" + "testing" + + "github.com/Masterminds/semver/v3" ) var ( @@ -38,11 +42,10 @@ var ( gitCommit = "" // gitTreeState is the state of the git tree gitTreeState = "" +) - // The Kubernetes version can be set by LDFLAGS. In order to do that the value - // must be a string. - kubeClientVersionMajor = "" - kubeClientVersionMinor = "" +const ( + kubeClientGoVersionTesting = "v1.20" ) // BuildInfo describes the compile time information. @@ -74,12 +77,39 @@ func GetUserAgent() string { // Get returns build info func Get() BuildInfo { + + makeKubeClientVersionString := func() string { + // Test builds don't include debug info / module info + // (And even if they did, we probably want a stable version during tests anyway) + // Return a default value for test builds + if testing.Testing() { + return kubeClientGoVersionTesting + } + + vstr, err := K8sIOClientGoModVersion() + if err != nil { + slog.Error("failed to retrieve k8s.io/client-go version", slog.Any("error", err)) + return "" + } + + v, err := semver.NewVersion(vstr) + if err != nil { + slog.Error("unable to parse k8s.io/client-go version", slog.String("version", vstr), slog.Any("error", err)) + return "" + } + + kubeClientVersionMajor := v.Major() + 1 + kubeClientVersionMinor := v.Minor() + + return fmt.Sprintf("v%d.%d", kubeClientVersionMajor, kubeClientVersionMinor) + } + v := BuildInfo{ Version: GetVersion(), GitCommit: gitCommit, GitTreeState: gitTreeState, GoVersion: runtime.Version(), - KubeClientVersion: fmt.Sprintf("v%s.%s", kubeClientVersionMajor, kubeClientVersionMinor), + KubeClientVersion: makeKubeClientVersionString(), } // HACK(bacongobbler): strip out GoVersion during a test run for consistent test output diff --git a/pkg/chart/common/capabilities.go b/pkg/chart/common/capabilities.go index 9953abaf5..18d00de90 100644 --- a/pkg/chart/common/capabilities.go +++ b/pkg/chart/common/capabilities.go @@ -20,7 +20,9 @@ import ( "slices" "strconv" "strings" + "testing" + "github.com/Masterminds/semver/v3" "k8s.io/client-go/kubernetes/scheme" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -30,27 +32,23 @@ import ( helmversion "helm.sh/helm/v4/internal/version" ) -var ( - // The Kubernetes version can be set by LDFLAGS. In order to do that the value - // must be a string. - k8sVersionMajor = "1" - k8sVersionMinor = "20" +const ( + kubeVersionMajorTesting = 1 + kubeVersionMinorTesting = 20 +) +var ( // DefaultVersionSet is the default version set, which includes only Core V1 ("v1"). DefaultVersionSet = allKnownVersions() - // DefaultCapabilities is the default set of capabilities. - version = fmt.Sprintf("v%s.%s.0", k8sVersionMajor, k8sVersionMinor) - DefaultCapabilities = &Capabilities{ - KubeVersion: KubeVersion{ - Version: version, - normalizedVersion: version, - Major: k8sVersionMajor, - Minor: k8sVersionMinor, - }, - APIVersions: DefaultVersionSet, - HelmVersion: helmversion.Get(), - } + DefaultCapabilities = func() *Capabilities { + caps, err := makeDefaultCapabilities() + if err != nil { + panic(fmt.Sprintf("failed to create default capabilities: %v", err)) + } + return caps + + }() ) // Capabilities describes the capabilities of the Kubernetes cluster. @@ -143,3 +141,42 @@ func allKnownVersions() VersionSet { } return vs } + +func makeDefaultCapabilities() (*Capabilities, error) { + // Test builds don't include debug info / module info + // (And even if they did, we probably want stable capabilities for tests anyway) + // Return a default value for test builds + if testing.Testing() { + return newCapabilities(kubeVersionMajorTesting, kubeVersionMinorTesting) + } + + vstr, err := helmversion.K8sIOClientGoModVersion() + if err != nil { + return nil, fmt.Errorf("failed to retrieve k8s.io/client-go version: %w", err) + } + + v, err := semver.NewVersion(vstr) + if err != nil { + return nil, fmt.Errorf("unable to parse k8s.io/client-go version %q: %v", vstr, err) + } + + kubeVersionMajor := v.Major() + 1 + kubeVersionMinor := v.Minor() + + return newCapabilities(kubeVersionMajor, kubeVersionMinor) +} + +func newCapabilities(kubeVersionMajor, kubeVersionMinor uint64) (*Capabilities, error) { + + version := fmt.Sprintf("v%d.%d.0", kubeVersionMajor, kubeVersionMinor) + return &Capabilities{ + KubeVersion: KubeVersion{ + Version: version, + normalizedVersion: version, + Major: fmt.Sprintf("%d", kubeVersionMajor), + Minor: fmt.Sprintf("%d", kubeVersionMinor), + }, + APIVersions: DefaultVersionSet, + HelmVersion: helmversion.Get(), + }, nil +} diff --git a/pkg/chart/common/capabilities_test.go b/pkg/chart/common/capabilities_test.go index f29c2c752..b96d7d29b 100644 --- a/pkg/chart/common/capabilities_test.go +++ b/pkg/chart/common/capabilities_test.go @@ -41,7 +41,8 @@ func TestDefaultVersionSet(t *testing.T) { } func TestDefaultCapabilities(t *testing.T) { - kv := DefaultCapabilities.KubeVersion + caps := DefaultCapabilities + kv := caps.KubeVersion if kv.String() != "v1.20.0" { t.Errorf("Expected default KubeVersion.String() to be v1.20.0, got %q", kv.String()) } @@ -57,11 +58,8 @@ func TestDefaultCapabilities(t *testing.T) { if kv.Minor != "20" { t.Errorf("Expected default KubeVersion.Minor to be 20, got %q", kv.Minor) } -} - -func TestDefaultCapabilitiesHelmVersion(t *testing.T) { - hv := DefaultCapabilities.HelmVersion + hv := caps.HelmVersion if hv.Version != "v4.1" { t.Errorf("Expected default HelmVersion to be v4.1, got %q", hv.Version) } diff --git a/pkg/chart/v2/lint/lint_test.go b/pkg/chart/v2/lint/lint_test.go index 6f8f137f4..80dcef932 100644 --- a/pkg/chart/v2/lint/lint_test.go +++ b/pkg/chart/v2/lint/lint_test.go @@ -179,16 +179,6 @@ func TestHelmCreateChart(t *testing.T) { // // Resources like hpa and ingress, which are disabled by default in values.yaml are enabled here using the equivalent // of the `--set` flag. -// -// Note: This test requires the following ldflags to be set per the current Kubernetes version to avoid false-positive -// results. -// 1. -X helm.sh/helm/v4/pkg/lint/rules.k8sVersionMajor= -// 2. -X helm.sh/helm/v4/pkg/lint/rules.k8sVersionMinor= -// or directly use '$(LDFLAGS)' in Makefile. -// -// When run without ldflags, the test passes giving a false-positive result. This is because the variables -// `k8sVersionMajor` and `k8sVersionMinor` by default are set to an older version of Kubernetes, with which, there -// might not be the deprecation warning. func TestHelmCreateChart_CheckDeprecatedWarnings(t *testing.T) { createdChart, err := chartutil.Create("checkdeprecatedwarnings", t.TempDir()) if err != nil { diff --git a/pkg/chart/v2/lint/rules/deprecations.go b/pkg/chart/v2/lint/rules/deprecations.go index 34bd361bf..7d5245869 100644 --- a/pkg/chart/v2/lint/rules/deprecations.go +++ b/pkg/chart/v2/lint/rules/deprecations.go @@ -28,15 +28,7 @@ import ( kscheme "k8s.io/client-go/kubernetes/scheme" ) -var ( - // This should be set in the Makefile based on the version of client-go being imported. - // These constants will be overwritten with LDFLAGS. The version components must be - // strings in order for LDFLAGS to set them. - k8sVersionMajor = "1" - k8sVersionMinor = "20" -) - -// deprecatedAPIError indicates that an API is deprecated in Kubernetes +// deprecatedAPIError indicates than an API is deprecated in Kubernetes type deprecatedAPIError struct { Deprecated string Message string @@ -56,12 +48,8 @@ func validateNoDeprecations(resource *k8sYamlStruct, kubeVersion *common.KubeVer return nil } - majorVersion := k8sVersionMajor - minorVersion := k8sVersionMinor - - if kubeVersion != nil { - majorVersion = kubeVersion.Major - minorVersion = kubeVersion.Minor + if kubeVersion == nil { + kubeVersion = &common.DefaultCapabilities.KubeVersion } runtimeObject, err := resourceToRuntimeObject(resource) @@ -73,16 +61,16 @@ func validateNoDeprecations(resource *k8sYamlStruct, kubeVersion *common.KubeVer return err } - major, err := strconv.Atoi(majorVersion) + kubeVersionMajor, err := strconv.Atoi(kubeVersion.Major) if err != nil { return err } - minor, err := strconv.Atoi(minorVersion) + kubeVersionMinor, err := strconv.Atoi(kubeVersion.Minor) if err != nil { return err } - if !deprecation.IsDeprecated(runtimeObject, major, minor) { + if !deprecation.IsDeprecated(runtimeObject, kubeVersionMajor, kubeVersionMinor) { return nil } gvk := fmt.Sprintf("%s %s", resource.APIVersion, resource.Kind) diff --git a/pkg/cmd/lint_test.go b/pkg/cmd/lint_test.go index 270273116..f825e36e2 100644 --- a/pkg/cmd/lint_test.go +++ b/pkg/cmd/lint_test.go @@ -76,7 +76,7 @@ func TestLintCmdWithKubeVersionFlag(t *testing.T) { golden: "output/lint-chart-with-deprecated-api-strict.txt", wantError: true, }, { - // the test builds will use the default k8sVersionMinor const in deprecations.go and capabilities.go + // the test builds will use the kubeVersionMinorTesting const in capabilities.go // which is "20" name: "lint chart with deprecated api version without kube version", cmd: fmt.Sprintf("lint %s", testChart), diff --git a/pkg/cmd/testdata/output/version.txt b/pkg/cmd/testdata/output/version.txt index 44c5bf470..1f4cf4d4a 100644 --- a/pkg/cmd/testdata/output/version.txt +++ b/pkg/cmd/testdata/output/version.txt @@ -1 +1 @@ -version.BuildInfo{Version:"v4.1", GitCommit:"", GitTreeState:"", GoVersion:"", KubeClientVersion:"v."} +version.BuildInfo{Version:"v4.1", GitCommit:"", GitTreeState:"", GoVersion:"", KubeClientVersion:"v1.20"} From 94860626ce9c83a9227b5bce02a5c03a050816ac Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Sat, 20 Dec 2025 21:49:25 +0530 Subject: [PATCH 107/571] fix: error handled correctly Signed-off-by: Mujib Ahasan --- pkg/action/validate.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/action/validate.go b/pkg/action/validate.go index 8e58af7f0..df06d0105 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -47,7 +47,7 @@ func requireAdoption(resources kube.ResourceList) (kube.ResourceList, error) { } isGenerateName, err := validateNameAndgenerateName(info) - if isGenerateName { + if isGenerateName || err != nil { return err } @@ -76,7 +76,7 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN } isGenerateName, err := validateNameAndgenerateName(info) - if isGenerateName { + if isGenerateName || err != nil { return err } @@ -216,7 +216,7 @@ func mergeStrStrMaps(current, desired map[string]string) map[string]string { func validateNameAndgenerateName(info *resource.Info) (bool, error) { accessor, err := meta.Accessor(info.Object) if err != nil { - return true, err + return false, err } if info.Name == "" && accessor.GetGenerateName() != "" { From 1c119bcaa6f68a73f27a21f9d7dba22c4baf4d7d Mon Sep 17 00:00:00 2001 From: Brenden Ehlers Date: Sun, 21 Dec 2025 08:44:32 -0700 Subject: [PATCH 108/571] fix: add default casess to switch statements Signed-off-by: Brenden Ehlers --- .golangci.yml | 3 +++ internal/statusreaders/pod_status_reader.go | 28 ++++++++++----------- pkg/cmd/repo_list.go | 8 +++--- pkg/cmd/search_hub.go | 7 +++--- pkg/cmd/search_repo.go | 8 +++--- pkg/kube/ready.go | 4 +++ pkg/kube/wait.go | 2 ++ 7 files changed, 35 insertions(+), 25 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index baa3d96e7..5f08f25e8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -73,6 +73,9 @@ linters: recommendations: - github.com/evanphx/json-patch/v5 + exhaustive: + default-signifies-exhaustive: true + run: timeout: 10m diff --git a/internal/statusreaders/pod_status_reader.go b/internal/statusreaders/pod_status_reader.go index c074c3487..bf633c0dd 100644 --- a/internal/statusreaders/pod_status_reader.go +++ b/internal/statusreaders/pod_status_reader.go @@ -86,19 +86,19 @@ func podConditions(u *unstructured.Unstructured) (*status.Result, error) { }, }, }, nil - } - - message := "Pod in progress" - return &status.Result{ - Status: status.InProgressStatus, - Message: message, - Conditions: []status.Condition{ - { - Type: status.ConditionReconciling, - Status: corev1.ConditionTrue, - Reason: "PodInProgress", - Message: message, + default: + message := "Pod in progress" + return &status.Result{ + Status: status.InProgressStatus, + Message: message, + Conditions: []status.Condition{ + { + Type: status.ConditionReconciling, + Status: corev1.ConditionTrue, + Reason: "PodInProgress", + Message: message, + }, }, - }, - }, nil + }, nil + } } diff --git a/pkg/cmd/repo_list.go b/pkg/cmd/repo_list.go index 60f4cb6c6..450294948 100644 --- a/pkg/cmd/repo_list.go +++ b/pkg/cmd/repo_list.go @@ -103,11 +103,11 @@ func (r *repoListWriter) encodeByFormat(out io.Writer, format output.Format) err return output.EncodeJSON(out, repolist) case output.YAML: return output.EncodeYAML(out, repolist) + default: + // Because this is a non-exported function and only called internally by + // WriteJSON and WriteYAML, we shouldn't get invalid types + return nil } - - // Because this is a non-exported function and only called internally by - // WriteJSON and WriteYAML, we shouldn't get invalid types - return nil } // Returns all repos from repos, except those with names matching ignoredRepoNames diff --git a/pkg/cmd/search_hub.go b/pkg/cmd/search_hub.go index cfeeec59b..bb2ff6038 100644 --- a/pkg/cmd/search_hub.go +++ b/pkg/cmd/search_hub.go @@ -190,9 +190,10 @@ func (h *hubSearchWriter) encodeByFormat(out io.Writer, format output.Format) er return output.EncodeJSON(out, chartList) case output.YAML: return output.EncodeYAML(out, chartList) + default: + // Because this is a non-exported function and only called internally by + // WriteJSON and WriteYAML, we shouldn't get invalid types + return nil } - // Because this is a non-exported function and only called internally by - // WriteJSON and WriteYAML, we shouldn't get invalid types - return nil } diff --git a/pkg/cmd/search_repo.go b/pkg/cmd/search_repo.go index d54e85d91..febb138e2 100644 --- a/pkg/cmd/search_repo.go +++ b/pkg/cmd/search_repo.go @@ -260,11 +260,11 @@ func (r *repoSearchWriter) encodeByFormat(out io.Writer, format output.Format) e return output.EncodeJSON(out, chartList) case output.YAML: return output.EncodeYAML(out, chartList) + default: + // Because this is a non-exported function and only called internally by + // WriteJSON and WriteYAML, we shouldn't get invalid types + return nil } - - // Because this is a non-exported function and only called internally by - // WriteJSON and WriteYAML, we shouldn't get invalid types - return nil } // Provides the list of charts that are part of the specified repo, and that starts with 'prefix'. diff --git a/pkg/kube/ready.go b/pkg/kube/ready.go index 42e327bdd..bfa98504c 100644 --- a/pkg/kube/ready.go +++ b/pkg/kube/ready.go @@ -354,6 +354,8 @@ func (c *ReadyChecker) crdBetaReady(crd apiextv1beta1.CustomResourceDefinition) // continue. return true } + default: + // intentionally left empty } } return false @@ -374,6 +376,8 @@ func (c *ReadyChecker) crdReady(crd apiextv1.CustomResourceDefinition) bool { // continue. return true } + default: + // intentionally left empty } } return false diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go index 354915b29..9a276a459 100644 --- a/pkg/kube/wait.go +++ b/pkg/kube/wait.go @@ -333,6 +333,8 @@ func (hw *legacyWaiter) waitForPodSuccess(obj runtime.Object, name string) (bool slog.Debug("pod pending", "pod", o.Name) case corev1.PodRunning: slog.Debug("pod running", "pod", o.Name) + case corev1.PodUnknown: + slog.Debug("pod unknown", "pod", o.Name) } return false, nil From 9a898af7f22cf82a807c33617ef0d6593674aafb Mon Sep 17 00:00:00 2001 From: Brenden Ehlers Date: Mon, 22 Dec 2025 07:40:40 -0700 Subject: [PATCH 109/571] enable exhaustive linter Signed-off-by: Brenden Ehlers --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index 5f08f25e8..7eca135e5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -32,6 +32,7 @@ linters: - unused - usestdlibvars - usetesting + - exhaustive exclusions: From 170911459bc4f2b5efea7e549e09bd45c7578cc4 Mon Sep 17 00:00:00 2001 From: Mujib Ahasan Date: Tue, 23 Dec 2025 03:19:41 +0530 Subject: [PATCH 110/571] fix: casing issue fixed Signed-off-by: Mujib Ahasan --- pkg/action/validate.go | 8 ++++---- pkg/action/validate_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/action/validate.go b/pkg/action/validate.go index df06d0105..5894c229a 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -46,7 +46,7 @@ func requireAdoption(resources kube.ResourceList) (kube.ResourceList, error) { return err } - isGenerateName, err := validateNameAndgenerateName(info) + isGenerateName, err := validateNameAndGenerateName(info) if isGenerateName || err != nil { return err } @@ -75,7 +75,7 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN return err } - isGenerateName, err := validateNameAndgenerateName(info) + isGenerateName, err := validateNameAndGenerateName(info) if isGenerateName || err != nil { return err } @@ -210,10 +210,10 @@ func mergeStrStrMaps(current, desired map[string]string) map[string]string { return result } -// validateNameAndgenerateName validates that an object only has either `Name` or `GenerateName` set (and not both) +// validateNameAndGenerateName validates that an object only has either `Name` or `GenerateName` set (and not both) // If `GenerateName` is set, true is returned // If an invalid combination of `Name` and `GenerateName` are set, an error is returned -func validateNameAndgenerateName(info *resource.Info) (bool, error) { +func validateNameAndGenerateName(info *resource.Info) (bool, error) { accessor, err := meta.Accessor(info.Object) if err != nil { return false, err diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go index 32ee8d802..6bf4ec533 100644 --- a/pkg/action/validate_test.go +++ b/pkg/action/validate_test.go @@ -272,7 +272,7 @@ func TestValidateNameAndGenerateName(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - skip, err := validateNameAndgenerateName(tc.info) + skip, err := validateNameAndGenerateName(tc.info) if tc.wantErr { assert.Error(t, err) From 268593bf2e9769ef4b75328b33dfb4195e6e9e5a Mon Sep 17 00:00:00 2001 From: Manuel Alonso <434575+manute@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:21:02 +0100 Subject: [PATCH 111/571] fix(action): crd resources can be empty Signed-off-by: Manuel Alonso <434575+manute@users.noreply.github.com> --- pkg/action/install.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/action/install.go b/pkg/action/install.go index 2f5910284..b0c28132a 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -186,6 +186,10 @@ func (i *Install) installCRDs(crds []chart.CRD) error { return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err) } + if res == nil { + return fmt.Errorf("failed to install CRD %s: resources are empty", obj.Name) + } + // Send them to Kube if _, err := i.cfg.KubeClient.Create( res, From 52235cc0bf7d0c8faf17c7dc8cddd77f93434aea Mon Sep 17 00:00:00 2001 From: Manuel Alonso <434575+manute@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:33:20 +0100 Subject: [PATCH 112/571] fix(install): check lenght and file nil, add tests Signed-off-by: Manuel Alonso <434575+manute@users.noreply.github.com> --- pkg/action/install.go | 6 +++++- pkg/action/install_test.go | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index b0c28132a..b2e8f8bf4 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -180,13 +180,17 @@ func (i *Install) installCRDs(crds []chart.CRD) error { // We do these one file at a time in the order they were read. totalItems := []*resource.Info{} for _, obj := range crds { + if obj.File == nil { + return fmt.Errorf("failed to install CRD %s: file is empty", obj.Name) + } + // Read in the resources res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.File.Data), false) if err != nil { return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err) } - if res == nil { + if len(res) == 0 { return fmt.Errorf("failed to install CRD %s: resources are empty", obj.Name) } diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 9f04f40d4..a9a33881a 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -45,6 +45,7 @@ import ( "helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/pkg/chart/common" + chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/kube" kubefake "helm.sh/helm/v4/pkg/kube/fake" rcommon "helm.sh/helm/v4/pkg/release/common" @@ -1068,3 +1069,42 @@ func TestInstallRun_UnreachableKubeClient(t *testing.T) { assert.Nil(t, res) assert.ErrorContains(t, err, "connection refused") } + +func TestInstallCRDs_CheckNilErrors(t *testing.T) { + tests := []struct { + name string + input []chart.CRD + }{ + { + name: "only one crd with file nil", + input: []chart.CRD{ + {Name: "one", File: nil}, + }, + }, + { + name: "only one crd with its file data nil", + input: []chart.CRD{ + {Name: "one", File: &common.File{Name: "crds/foo.yaml", Data: nil}}, + }, + }, + { + name: "at least a crd with its file data nil", + input: []chart.CRD{ + {Name: "one", File: &common.File{Name: "crds/foo.yaml", Data: []byte("data")}}, + {Name: "two", File: &common.File{Name: "crds/foo.yaml", Data: nil}}, + {Name: "three", File: &common.File{Name: "crds/foo.yaml", Data: []byte("data")}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + instAction := installAction(t) + + err := instAction.installCRDs(tt.input) + if err == nil { + t.Errorf("got error expected nil") + } + }) + } +} From 0357e8d0f7eab074252ca49e1ca3aded834a001d Mon Sep 17 00:00:00 2001 From: Manuel Alonso <434575+manute@users.noreply.github.com> Date: Mon, 22 Dec 2025 19:15:16 +0100 Subject: [PATCH 113/571] fix(test): no check empty resources Signed-off-by: Manuel Alonso <434575+manute@users.noreply.github.com> --- pkg/action/install.go | 4 ---- pkg/action/install_test.go | 16 ++++++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index b2e8f8bf4..57839b289 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -190,10 +190,6 @@ func (i *Install) installCRDs(crds []chart.CRD) error { return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err) } - if len(res) == 0 { - return fmt.Errorf("failed to install CRD %s: resources are empty", obj.Name) - } - // Send them to Kube if _, err := i.cfg.KubeClient.Create( res, diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index a9a33881a..38ea556f5 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -1072,28 +1072,32 @@ func TestInstallRun_UnreachableKubeClient(t *testing.T) { func TestInstallCRDs_CheckNilErrors(t *testing.T) { tests := []struct { - name string - input []chart.CRD + name string + input []chart.CRD + expectedErr bool }{ { name: "only one crd with file nil", input: []chart.CRD{ {Name: "one", File: nil}, }, + expectedErr: true, }, { name: "only one crd with its file data nil", input: []chart.CRD{ {Name: "one", File: &common.File{Name: "crds/foo.yaml", Data: nil}}, }, + expectedErr: false, }, { name: "at least a crd with its file data nil", input: []chart.CRD{ {Name: "one", File: &common.File{Name: "crds/foo.yaml", Data: []byte("data")}}, - {Name: "two", File: &common.File{Name: "crds/foo.yaml", Data: nil}}, - {Name: "three", File: &common.File{Name: "crds/foo.yaml", Data: []byte("data")}}, + {Name: "two", File: &common.File{Name: "crds/foo2.yaml", Data: nil}}, + {Name: "three", File: &common.File{Name: "crds/foo3.yaml", Data: []byte("data")}}, }, + expectedErr: false, }, } @@ -1102,8 +1106,8 @@ func TestInstallCRDs_CheckNilErrors(t *testing.T) { instAction := installAction(t) err := instAction.installCRDs(tt.input) - if err == nil { - t.Errorf("got error expected nil") + if tt.expectedErr && err == nil { + t.Errorf("got error %v expected nil", err) } }) } From 00f0a48a7dae379c2b6bd0dea43984d42b27a494 Mon Sep 17 00:00:00 2001 From: Manuel Alonso <434575+manute@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:35:14 +0100 Subject: [PATCH 114/571] fix(install): add more tests and check nil file data Signed-off-by: Manuel Alonso <434575+manute@users.noreply.github.com> --- pkg/action/install.go | 8 ++++++++ pkg/action/install_test.go | 12 ++++-------- pkg/cmd/install_test.go | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index 57839b289..e832bef0a 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -184,12 +184,20 @@ func (i *Install) installCRDs(crds []chart.CRD) error { return fmt.Errorf("failed to install CRD %s: file is empty", obj.Name) } + if obj.File.Data == nil { + return fmt.Errorf("failed to install CRD %s: file data is empty", obj.Name) + } + // Read in the resources res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.File.Data), false) if err != nil { return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err) } + if len(res) == 0 { + return fmt.Errorf("failed to install CRD %s: resources are empty", obj.Name) + } + // Send them to Kube if _, err := i.cfg.KubeClient.Create( res, diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 38ea556f5..11b175df8 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -1072,23 +1072,20 @@ func TestInstallRun_UnreachableKubeClient(t *testing.T) { func TestInstallCRDs_CheckNilErrors(t *testing.T) { tests := []struct { - name string - input []chart.CRD - expectedErr bool + name string + input []chart.CRD }{ { name: "only one crd with file nil", input: []chart.CRD{ {Name: "one", File: nil}, }, - expectedErr: true, }, { name: "only one crd with its file data nil", input: []chart.CRD{ {Name: "one", File: &common.File{Name: "crds/foo.yaml", Data: nil}}, }, - expectedErr: false, }, { name: "at least a crd with its file data nil", @@ -1097,7 +1094,6 @@ func TestInstallCRDs_CheckNilErrors(t *testing.T) { {Name: "two", File: &common.File{Name: "crds/foo2.yaml", Data: nil}}, {Name: "three", File: &common.File{Name: "crds/foo3.yaml", Data: []byte("data")}}, }, - expectedErr: false, }, } @@ -1106,8 +1102,8 @@ func TestInstallCRDs_CheckNilErrors(t *testing.T) { instAction := installAction(t) err := instAction.installCRDs(tt.input) - if tt.expectedErr && err == nil { - t.Errorf("got error %v expected nil", err) + if err == nil { + t.Errorf("got nil expected err") } }) } diff --git a/pkg/cmd/install_test.go b/pkg/cmd/install_test.go index f0f12e4f7..5fa3c1340 100644 --- a/pkg/cmd/install_test.go +++ b/pkg/cmd/install_test.go @@ -240,7 +240,7 @@ func TestInstall(t *testing.T) { // Install chart with only crds { name: "install chart with only crds", - cmd: "install crd-test testdata/testcharts/chart-with-only-crds --namespace default", + cmd: "install crd-test testdata/testcharts/chart-with-only-crds --namespace default --dry-run", }, // Verify the user/pass works { From 04198dcf8a5dd3b80785ee8157c555e30d0071b9 Mon Sep 17 00:00:00 2001 From: jylenhof Date: Tue, 23 Dec 2025 21:42:38 +0100 Subject: [PATCH 115/571] docs(README): add mise alternate installation documentation Signed-off-by: jylenhof --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 22422d5a4..37bc8abaa 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ If you want to use a package manager: - [Scoop](https://scoop.sh/) users can use `scoop install helm`. - [Snapcraft](https://snapcraft.io/) users can use `snap install helm --classic`. - [Flox](https://flox.dev) users can use `flox install kubernetes-helm`. +- [Mise-en-place](https://mise.jdx.dev/) users can use `mise use -g helm@latest` To rapidly get Helm up and running, start with the [Quick Start Guide](https://helm.sh/docs/intro/quickstart/). From a15db7f087826312eff5fddf77b5956521fbaa6b Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Fri, 26 Dec 2025 08:36:42 -0800 Subject: [PATCH 116/571] Replace deprecated `NewSimpleClientset` Signed-off-by: George Jenkins --- pkg/kube/client_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index f3a797246..d442864f8 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -923,7 +923,7 @@ func TestGetPodList(t *testing.T) { responsePodList.Items = append(responsePodList.Items, newPodWithStatus(name, v1.PodStatus{}, namespace)) } - kubeClient := k8sfake.NewSimpleClientset(&responsePodList) + kubeClient := k8sfake.NewClientset(&responsePodList) c := Client{Namespace: namespace, kubeClient: kubeClient} podList, err := c.GetPodList(namespace, metav1.ListOptions{}) @@ -936,7 +936,7 @@ func TestOutputContainerLogsForPodList(t *testing.T) { namespace := "some-namespace" somePodList := newPodList("jimmy", "three", "structs") - kubeClient := k8sfake.NewSimpleClientset(&somePodList) + kubeClient := k8sfake.NewClientset(&somePodList) c := Client{Namespace: namespace, kubeClient: kubeClient} outBuffer := &bytes.Buffer{} outBufferFunc := func(_, _, _ string) io.Writer { return outBuffer } @@ -1314,7 +1314,7 @@ func TestIsReachable(t *testing.T) { setupClient: func(t *testing.T) *Client { t.Helper() client := newTestClient(t) - client.kubeClient = k8sfake.NewSimpleClientset() + client.kubeClient = k8sfake.NewClientset() return client }, expectError: false, From 5e82698d578a52f6e13b096747e7674d2f27fc20 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 27 Dec 2025 03:22:18 +0000 Subject: [PATCH 117/571] chore(deps): bump github.com/fluxcd/cli-utils Bumps [github.com/fluxcd/cli-utils](https://github.com/fluxcd/cli-utils) from 0.36.0-flux.15 to 0.37.0-flux.1. - [Commits](https://github.com/fluxcd/cli-utils/compare/v0.36.0-flux.15...v0.37.0-flux.1) --- updated-dependencies: - dependency-name: github.com/fluxcd/cli-utils dependency-version: 0.37.0-flux.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 49 ++++++++++------------ go.sum | 126 +++++++++++++++++++++------------------------------------ 2 files changed, 68 insertions(+), 107 deletions(-) diff --git a/go.mod b/go.mod index f5125a83f..128ee6573 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.18.0 - github.com/fluxcd/cli-utils v0.36.0-flux.15 + github.com/fluxcd/cli-utils v0.37.0-flux.1 github.com/foxcpp/go-mockdns v1.1.0 github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.13.0 @@ -39,14 +39,14 @@ require ( golang.org/x/term v0.38.0 golang.org/x/text v0.32.0 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.3 - k8s.io/apiextensions-apiserver v0.34.3 - k8s.io/apimachinery v0.34.3 - k8s.io/apiserver v0.34.3 - k8s.io/cli-runtime v0.34.3 - k8s.io/client-go v0.34.3 + k8s.io/api v0.35.0 + k8s.io/apiextensions-apiserver v0.35.0 + k8s.io/apimachinery v0.35.0 + k8s.io/apiserver v0.35.0 + k8s.io/cli-runtime v0.35.0 + k8s.io/client-go v0.35.0 k8s.io/klog/v2 v2.130.1 - k8s.io/kubectl v0.34.3 + k8s.io/kubectl v0.35.0 oras.land/oras-go/v2 v2.6.0 sigs.k8s.io/controller-runtime v0.22.4 sigs.k8s.io/kustomize/kyaml v0.21.0 @@ -85,14 +85,12 @@ require ( github.com/go-openapi/jsonpointer v0.21.1 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect @@ -114,19 +112,16 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/spdystream v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/onsi/gomega v1.38.2 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.17.0 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect @@ -141,7 +136,7 @@ require ( go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 // indirect go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 // indirect @@ -156,12 +151,12 @@ require ( go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 // indirect go.opentelemetry.io/otel/log v0.8.0 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.36.0 // indirect go.opentelemetry.io/otel/sdk/log v0.8.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect @@ -170,16 +165,16 @@ require ( golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.39.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/grpc v1.72.1 // indirect - google.golang.org/protobuf v1.36.7 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect + google.golang.org/grpc v1.72.2 // indirect + google.golang.org/protobuf v1.36.8 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/component-base v0.34.3 // indirect - k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/component-base v0.35.0 // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.20.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect diff --git a/go.sum b/go.sum index f94667c2f..446081173 100644 --- a/go.sum +++ b/go.sum @@ -26,8 +26,6 @@ github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBi github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -93,8 +91,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/cli-utils v0.36.0-flux.15 h1:Et5QLnIpRjj+oZtM9gEybkAaoNsjysHq0y1253Ai94Y= -github.com/fluxcd/cli-utils v0.36.0-flux.15/go.mod h1:AqRUmWIfNE7cdL6NWSGF0bAlypGs+9x5UQ2qOtlEzv4= +github.com/fluxcd/cli-utils v0.37.0-flux.1 h1:k/VvPNT3tGa/l2N+qzHduaQr3GVbgoWS6nw7tGZz16w= +github.com/fluxcd/cli-utils v0.37.0-flux.1/go.mod h1:aND5wX3LuTFtB7eUT7vsWr8mmxRVSPR2Wkvbn0SqPfw= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -132,8 +130,6 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -155,8 +151,6 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= -github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= @@ -182,8 +176,6 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= @@ -225,8 +217,6 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -242,10 +232,8 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo/v2 v2.25.2 h1:hepmgwx1D+llZleKQDMEvy8vIlCxMGt7W5ZxDjIEhsw= -github.com/onsi/ginkgo/v2 v2.25.2/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= +github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= +github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -255,8 +243,6 @@ github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgr github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -265,16 +251,16 @@ github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjz github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= @@ -327,8 +313,6 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= @@ -336,8 +320,8 @@ go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGh go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= @@ -366,40 +350,34 @@ go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWer go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= -go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -407,11 +385,8 @@ golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -426,8 +401,6 @@ golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKl golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= @@ -438,9 +411,7 @@ golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -478,8 +449,6 @@ golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= @@ -487,23 +456,20 @@ golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= -google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -512,34 +478,34 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= -k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= -k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= -k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= -k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= -k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= -k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= -k8s.io/cli-runtime v0.34.3 h1:YRyMhiwX0dT9lmG0AtZDaeG33Nkxgt9OlCTZhRXj9SI= -k8s.io/cli-runtime v0.34.3/go.mod h1:GVwL1L5uaGEgM7eGeKjaTG2j3u134JgG4dAI6jQKhMc= -k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= -k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= -k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= -k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= +k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY= +k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA= +k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4= +k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU= +k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= +k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/apiserver v0.35.0 h1:CUGo5o+7hW9GcAEF3x3usT3fX4f9r8xmgQeCBDaOgX4= +k8s.io/apiserver v0.35.0/go.mod h1:QUy1U4+PrzbJaM3XGu2tQ7U9A4udRRo5cyxkFX0GEds= +k8s.io/cli-runtime v0.35.0 h1:PEJtYS/Zr4p20PfZSLCbY6YvaoLrfByd6THQzPworUE= +k8s.io/cli-runtime v0.35.0/go.mod h1:VBRvHzosVAoVdP3XwUQn1Oqkvaa8facnokNkD7jOTMY= +k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE= +k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o= +k8s.io/component-base v0.35.0 h1:+yBrOhzri2S1BVqyVSvcM3PtPyx5GUxCK2tinZz1G94= +k8s.io/component-base v0.35.0/go.mod h1:85SCX4UCa6SCFt6p3IKAPej7jSnF3L8EbfSyMZayJR0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= -k8s.io/kubectl v0.34.3 h1:vpM6//153gh5gvsYHXWHVJ4l4xmN5QFwTSmlfd8icm8= -k8s.io/kubectl v0.34.3/go.mod h1:zZQHtIZoUqTP1bAnPzq/3W1jfc0NeOeunFgcswrfg1c= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/kubectl v0.35.0 h1:cL/wJKHDe8E8+rP3G7avnymcMg6bH6JEcR5w5uo06wc= +k8s.io/kubectl v0.35.0/go.mod h1:VR5/TSkYyxZwrRwY5I5dDq6l5KXmiCb+9w8IKplk3Qo= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= sigs.k8s.io/kustomize/kyaml v0.21.0 h1:7mQAf3dUwf0wBerWJd8rXhVcnkk5Tvn/q91cGkaP6HQ= From fe1c749183e888a10b773757b20a6e85da373196 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Sat, 27 Dec 2025 10:25:08 -0500 Subject: [PATCH 118/571] Fixing failing tests for cli-tools update Tests were failing for cli-utils watcher because upstream k8s made changes that impacted cli-utils. In k8s WatchListClient is now enabled by default. Fake clients used for testing don't know this semantic. cli-utils leverages reflection in k8s to handle this. The Helm tests didn't handle this well. The tests are updated to use PrependReactor and PrependWatchReactor in the same way that cli-utils does for testing. This works without wrapping the client. Signed-off-by: Matt Farina --- pkg/kube/statuswait_test.go | 132 +++++++++++++++++------------------- 1 file changed, 62 insertions(+), 70 deletions(-) diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index 4e31ce31c..a8ff4e0e6 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -17,7 +17,6 @@ limitations under the License. package kube // import "helm.sh/helm/v3/pkg/kube" import ( - "context" "errors" "fmt" "strings" @@ -32,13 +31,13 @@ import ( v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/yaml" - "k8s.io/client-go/dynamic" + "k8s.io/apimachinery/pkg/watch" dynamicfake "k8s.io/client-go/dynamic/fake" + clienttesting "k8s.io/client-go/testing" "k8s.io/kubectl/pkg/scheme" ) @@ -646,79 +645,72 @@ func TestStatusWaitMultipleNamespaces(t *testing.T) { } } -type restrictedDynamicClient struct { - dynamic.Interface +// restrictedClientConfig holds the configuration for RBAC simulation on a fake dynamic client +type restrictedClientConfig struct { allowedNamespaces map[string]bool clusterScopedListAttempted bool } -func newRestrictedDynamicClient(baseClient dynamic.Interface, allowedNamespaces []string) *restrictedDynamicClient { +// setupRestrictedClient configures a fake dynamic client to simulate RBAC restrictions +// by using PrependReactor and PrependWatchReactor to intercept list/watch operations. +func setupRestrictedClient(fakeClient *dynamicfake.FakeDynamicClient, allowedNamespaces []string) *restrictedClientConfig { allowed := make(map[string]bool) for _, ns := range allowedNamespaces { allowed[ns] = true } - return &restrictedDynamicClient{ - Interface: baseClient, + config := &restrictedClientConfig{ allowedNamespaces: allowed, } -} - -func (r *restrictedDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface { - return &restrictedNamespaceableResource{ - NamespaceableResourceInterface: r.Interface.Resource(resource), - allowedNamespaces: r.allowedNamespaces, - clusterScopedListAttempted: &r.clusterScopedListAttempted, - } -} -type restrictedNamespaceableResource struct { - dynamic.NamespaceableResourceInterface - allowedNamespaces map[string]bool - clusterScopedListAttempted *bool -} - -func (r *restrictedNamespaceableResource) Namespace(ns string) dynamic.ResourceInterface { - return &restrictedResource{ - ResourceInterface: r.NamespaceableResourceInterface.Namespace(ns), - namespace: ns, - allowedNamespaces: r.allowedNamespaces, - clusterScopedListAttempted: r.clusterScopedListAttempted, - } -} - -func (r *restrictedNamespaceableResource) List(_ context.Context, _ metav1.ListOptions) (*unstructured.UnstructuredList, error) { - *r.clusterScopedListAttempted = true - return nil, apierrors.NewForbidden( - schema.GroupResource{Resource: "pods"}, - "", - fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources"), - ) -} - -type restrictedResource struct { - dynamic.ResourceInterface - namespace string - allowedNamespaces map[string]bool - clusterScopedListAttempted *bool -} + // Intercept list operations + fakeClient.PrependReactor("list", "*", func(action clienttesting.Action) (bool, runtime.Object, error) { + listAction := action.(clienttesting.ListAction) + ns := listAction.GetNamespace() + if ns == "" { + // Cluster-scoped list + config.clusterScopedListAttempted = true + return true, nil, apierrors.NewForbidden( + action.GetResource().GroupResource(), + "", + fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources"), + ) + } + if !config.allowedNamespaces[ns] { + return true, nil, apierrors.NewForbidden( + action.GetResource().GroupResource(), + "", + fmt.Errorf("user does not have LIST permissions in namespace %q", ns), + ) + } + // Fall through to the default handler + return false, nil, nil + }) + + // Intercept watch operations + fakeClient.PrependWatchReactor("*", func(action clienttesting.Action) (bool, watch.Interface, error) { + watchAction := action.(clienttesting.WatchAction) + ns := watchAction.GetNamespace() + if ns == "" { + // Cluster-scoped watch + config.clusterScopedListAttempted = true + return true, nil, apierrors.NewForbidden( + action.GetResource().GroupResource(), + "", + fmt.Errorf("user does not have cluster-wide WATCH permissions for cluster-scoped resources"), + ) + } + if !config.allowedNamespaces[ns] { + return true, nil, apierrors.NewForbidden( + action.GetResource().GroupResource(), + "", + fmt.Errorf("user does not have WATCH permissions in namespace %q", ns), + ) + } + // Fall through to the default handler + return false, nil, nil + }) -func (r *restrictedResource) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) { - if r.namespace == "" { - *r.clusterScopedListAttempted = true - return nil, apierrors.NewForbidden( - schema.GroupResource{Resource: "pods"}, - "", - fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources"), - ) - } - if !r.allowedNamespaces[r.namespace] { - return nil, apierrors.NewForbidden( - schema.GroupResource{Resource: "pods"}, - "", - fmt.Errorf("user does not have LIST permissions in namespace %q", r.namespace), - ) - } - return r.ResourceInterface.List(ctx, opts) + return config } func TestStatusWaitRestrictedRBAC(t *testing.T) { @@ -794,9 +786,9 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1"}.WithKind("ClusterRole"), v1.SchemeGroupVersion.WithKind("Namespace"), ) - restrictedClient := newRestrictedDynamicClient(baseFakeClient, tt.allowedNamespaces) + restrictedConfig := setupRestrictedClient(baseFakeClient, tt.allowedNamespaces) sw := statusWaiter{ - client: restrictedClient, + client: baseFakeClient, restMapper: fakeMapper, } objs := getRuntimeObjFromManifests(t, tt.objManifests) @@ -830,7 +822,7 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { return } assert.NoError(t, err) - assert.False(t, restrictedClient.clusterScopedListAttempted) + assert.False(t, restrictedConfig.clusterScopedListAttempted) }) } } @@ -901,9 +893,9 @@ func TestStatusWaitMixedResources(t *testing.T) { schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1"}.WithKind("ClusterRole"), v1.SchemeGroupVersion.WithKind("Namespace"), ) - restrictedClient := newRestrictedDynamicClient(baseFakeClient, tt.allowedNamespaces) + restrictedConfig := setupRestrictedClient(baseFakeClient, tt.allowedNamespaces) sw := statusWaiter{ - client: restrictedClient, + client: baseFakeClient, restMapper: fakeMapper, } objs := getRuntimeObjFromManifests(t, tt.objManifests) @@ -937,7 +929,7 @@ func TestStatusWaitMixedResources(t *testing.T) { return } assert.NoError(t, err) - assert.False(t, restrictedClient.clusterScopedListAttempted) + assert.False(t, restrictedConfig.clusterScopedListAttempted) }) } } From c7cc77b68d46b273bfb6b634d9c39b708c464221 Mon Sep 17 00:00:00 2001 From: majiayu000 <1835304752@qq.com> Date: Tue, 30 Dec 2025 23:41:29 +0800 Subject: [PATCH 119/571] Add documentation for --api-versions flag in template command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The --api-versions flag was undocumented in the template command's help text. This adds usage documentation and examples showing both comma-separated and multiple flag usage patterns. Fixes #13198 Signed-off-by: majiayu000 <1835304752@qq.com> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 Signed-off-by: majiayu000 <1835304752@qq.com> --- pkg/cmd/template.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index cf68c6c46..14f85042b 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -46,6 +46,16 @@ Render chart templates locally and display the output. Any values that would normally be looked up or retrieved in-cluster will be faked locally. Additionally, none of the server-side testing of chart validity (e.g. whether an API is supported) is done. + +To specify the Kubernetes API versions used for Capabilities.APIVersions, use +the '--api-versions' flag. This flag can be specified multiple times or as a +comma-separated list: + + $ helm template --api-versions networking.k8s.io/v1 --api-versions cert-manager.io/v1 mychart ./mychart + +or + + $ helm template --api-versions networking.k8s.io/v1,cert-manager.io/v1 mychart ./mychart ` func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { From 138f730aacf96d0d94535a1d5b29d6dd128a062e Mon Sep 17 00:00:00 2001 From: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> Date: Wed, 31 Dec 2025 07:46:52 +0100 Subject: [PATCH 120/571] fix: typo in the function names Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> --- internal/plugin/config.go | 7 +++---- internal/plugin/config_test.go | 4 ++-- internal/plugin/metadata.go | 7 +++---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/internal/plugin/config.go b/internal/plugin/config.go index e1f491779..785bde840 100644 --- a/internal/plugin/config.go +++ b/internal/plugin/config.go @@ -23,14 +23,13 @@ import ( "go.yaml.in/yaml/v3" ) -// Config represents an plugin type specific configuration -// It is expected to type assert (cast) the a Config to its expected underlying type (schema.ConfigCLIV1, schema.ConfigGetterV1, etc). +// Config represents a plugin type specific configuration +// It is expected to type assert (cast) the Config to its expected underlying type (schema.ConfigCLIV1, schema.ConfigGetterV1, etc). type Config interface { Validate() error } -func unmarshaConfig(pluginType string, configData map[string]any) (Config, error) { - +func unmarshalConfig(pluginType string, configData map[string]any) (Config, error) { pluginTypeMeta, ok := pluginTypesIndex[pluginType] if !ok { return nil, fmt.Errorf("unknown plugin type %q", pluginType) diff --git a/internal/plugin/config_test.go b/internal/plugin/config_test.go index c51b77ff0..beac05abf 100644 --- a/internal/plugin/config_test.go +++ b/internal/plugin/config_test.go @@ -27,7 +27,7 @@ import ( func TestUnmarshaConfig(t *testing.T) { // Test unmarshalling a CLI plugin config { - config, err := unmarshaConfig("cli/v1", map[string]any{ + config, err := unmarshalConfig("cli/v1", map[string]any{ "usage": "usage string", "shortHelp": "short help string", "longHelp": "long help string", @@ -46,7 +46,7 @@ func TestUnmarshaConfig(t *testing.T) { // Test unmarshalling invalid config data { - config, err := unmarshaConfig("cli/v1", map[string]any{ + config, err := unmarshalConfig("cli/v1", map[string]any{ "invalid field": "foo", }) require.Error(t, err) diff --git a/internal/plugin/metadata.go b/internal/plugin/metadata.go index b051c1223..4e019f0b3 100644 --- a/internal/plugin/metadata.go +++ b/internal/plugin/metadata.go @@ -174,13 +174,12 @@ func buildLegacyRuntimeConfig(m MetadataLegacy) RuntimeConfig { } func fromMetadataV1(mv1 MetadataV1) (*Metadata, error) { - - config, err := unmarshaConfig(mv1.Type, mv1.Config) + config, err := unmarshalConfig(mv1.Type, mv1.Config) if err != nil { return nil, err } - runtimeConfig, err := convertMetdataRuntimeConfig(mv1.Runtime, mv1.RuntimeConfig) + runtimeConfig, err := convertMetadataRuntimeConfig(mv1.Runtime, mv1.RuntimeConfig) if err != nil { return nil, err } @@ -197,7 +196,7 @@ func fromMetadataV1(mv1 MetadataV1) (*Metadata, error) { }, nil } -func convertMetdataRuntimeConfig(runtimeType string, runtimeConfigRaw map[string]any) (RuntimeConfig, error) { +func convertMetadataRuntimeConfig(runtimeType string, runtimeConfigRaw map[string]any) (RuntimeConfig, error) { var runtimeConfig RuntimeConfig var err error From 2d6d9c09c4d24aed5f945d648b8927de153ce470 Mon Sep 17 00:00:00 2001 From: Mads Jensen Date: Wed, 31 Dec 2025 10:04:52 +0100 Subject: [PATCH 121/571] Replace reflect.Ptr with reflect.Pointer The reflect package has a go:fix annotation for Ptr = Pointer. Signed-off-by: Mads Jensen --- pkg/chart/common.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/chart/common.go b/pkg/chart/common.go index 8080f3dc8..cd87e91e7 100644 --- a/pkg/chart/common.go +++ b/pkg/chart/common.go @@ -186,7 +186,7 @@ func structToMap(obj interface{}) (map[string]interface{}, error) { objValue := reflect.ValueOf(obj) // If the value is a pointer, dereference it - if objValue.Kind() == reflect.Ptr { + if objValue.Kind() == reflect.Pointer { objValue = objValue.Elem() } @@ -209,7 +209,7 @@ func structToMap(obj interface{}) (map[string]interface{}, error) { return nil, err } result[field.Name] = nestedMap - case reflect.Ptr: + case reflect.Pointer: // Recurse for pointers by dereferencing if value.IsNil() { result[field.Name] = nil @@ -224,7 +224,7 @@ func structToMap(obj interface{}) (map[string]interface{}, error) { sliceOfMaps := make([]interface{}, value.Len()) for j := 0; j < value.Len(); j++ { sliceElement := value.Index(j) - if sliceElement.Kind() == reflect.Struct || sliceElement.Kind() == reflect.Ptr { + if sliceElement.Kind() == reflect.Struct || sliceElement.Kind() == reflect.Pointer { nestedMap, err := structToMap(sliceElement.Interface()) if err != nil { return nil, err From fd090cce0c0e33e66321eb0ac504589441c34b31 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Thu, 1 Jan 2026 13:28:01 +0200 Subject: [PATCH 122/571] chore: move Evans Mungai from triage to maintainers Signed-off-by: Evans Mungai --- OWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OWNERS b/OWNERS index 17dd3997c..13827661a 100644 --- a/OWNERS +++ b/OWNERS @@ -1,4 +1,5 @@ maintainers: + - banjoh - gjenkins8 - joejulian - marckhouzam @@ -9,7 +10,6 @@ maintainers: - technosophos - TerryHowe triage: - - banjoh - yxxhero - zonggen - z4ce From 374aeb4b4e0463f72e3a0175138ed4bf7e87a156 Mon Sep 17 00:00:00 2001 From: Feruzjon Muyassarov Date: Wed, 22 Oct 2025 11:00:54 +0300 Subject: [PATCH 123/571] Fix rollback for missing resources Signed-off-by: Feruzjon Muyassarov --- pkg/kube/client.go | 31 +++++++++++++++++++++++++++-- pkg/kube/client_test.go | 44 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index d348423ab..e3937a923 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -532,7 +532,32 @@ func (c *Client) update(originals, targets ResourceList, updateApplyFunc UpdateA original := originals.Get(target) if original == nil { kind := target.Mapping.GroupVersionKind.Kind - return fmt.Errorf("original object %s with the name %q not found", kind, target.Name) + + slog.Warn("resource exists on cluster but not in original release, using cluster state as baseline", + "namespace", target.Namespace, "name", target.Name, "kind", kind) + + currentObj, err := helper.Get(target.Namespace, target.Name) + if err != nil { + return fmt.Errorf("original object %s with the name %q not found", kind, target.Name) + } + + // Create a temporary Info with the current cluster state to use as "original" + currentInfo := &resource.Info{ + Client: target.Client, + Mapping: target.Mapping, + Namespace: target.Namespace, + Name: target.Name, + Object: currentObj, + } + + if err := updateApplyFunc(currentInfo, target); err != nil { + updateErrors = append(updateErrors, err) + } + + // Because we check for errors later, append the info regardless + res.Updated = append(res.Updated, target) + + return nil } if err := updateApplyFunc(original, target); err != nil { @@ -570,7 +595,9 @@ func (c *Client) update(originals, targets ResourceList, updateApplyFunc UpdateA 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)) if !apierrors.IsNotFound(err) { - updateErrors = append(updateErrors, fmt.Errorf("failed to delete resource %s: %w", info.Name, err)) + updateErrors = append(updateErrors, fmt.Errorf( + "failed to delete resource namespace=%s, name=%s, kind=%s: %w", + info.Namespace, info.Name, info.Mapping.GroupVersionKind.Kind, err)) } continue } diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 3934171be..cc34e3360 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -412,7 +412,25 @@ func TestUpdate(t *testing.T) { "/namespaces/default/pods/forbidden:GET", "/namespaces/default/pods/forbidden:DELETE", ), - ExpectedError: "failed to delete resource forbidden:", + ExpectedError: "failed to delete resource namespace=default, name=forbidden, kind=Pod:", + }, + "rollback after failed upgrade with removed resource": { + // Simulates rollback scenario: + // - Revision 1 had "newpod" + // - Revision 2 removed "newpod" but upgrade failed (OriginalPods is empty) + // - Cluster still has "newpod" from Revision 1 + // - Rolling back to Revision 1 (TargetPods with "newpod") should succeed + OriginalPods: v1.PodList{}, // Revision 2 (failed) - resource was removed + TargetPods: newPodList("newpod"), // Revision 1 - rolling back to this + ThreeWayMergeForUnstructured: false, + ServerSideApply: true, + ExpectedActions: []string{ + "/namespaces/default/pods/newpod:GET", // Check if resource exists + "/namespaces/default/pods/newpod:GET", // Get current state (first call in update path) + "/namespaces/default/pods/newpod:GET", // Get current cluster state to use as baseline + "/namespaces/default/pods/newpod:PATCH", // Update using cluster state as baseline + }, + ExpectedError: "", }, } @@ -429,6 +447,10 @@ func TestUpdate(t *testing.T) { p, m := req.URL.Path, req.Method switch { + case p == "/namespaces/default/pods/newpod" && m == http.MethodGet: + return newResponse(http.StatusOK, &listTarget.Items[0]) + case p == "/namespaces/default/pods/newpod" && m == http.MethodPatch: + return newResponse(http.StatusOK, &listTarget.Items[0]) case p == "/namespaces/default/pods/starfish" && m == http.MethodGet: return newResponse(http.StatusOK, &listOriginal.Items[0]) case p == "/namespaces/default/pods/otter" && m == http.MethodGet: @@ -519,9 +541,23 @@ func TestUpdate(t *testing.T) { require.NoError(t, err) } - assert.Len(t, result.Created, 1, "expected 1 resource created, got %d", len(result.Created)) - assert.Len(t, result.Updated, 2, "expected 2 resource updated, got %d", len(result.Updated)) - assert.Len(t, result.Deleted, 1, "expected 1 resource deleted, got %d", len(result.Deleted)) + // Special handling for the rollback test case + if name == "rollback after failed upgrade with removed resource" { + assert.Len(t, result.Created, 0, "expected 0 resource created, got %d", len(result.Created)) + assert.Len(t, result.Updated, 1, "expected 1 resource updated, got %d", len(result.Updated)) + assert.Len(t, result.Deleted, 0, "expected 0 resource deleted, got %d", len(result.Deleted)) + } else { + assert.Len(t, result.Created, 1, "expected 1 resource created, got %d", len(result.Created)) + assert.Len(t, result.Updated, 2, "expected 2 resource updated, got %d", len(result.Updated)) + assert.Len(t, result.Deleted, 1, "expected 1 resource deleted, got %d", len(result.Deleted)) + } + + if tc.ExpectedError != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.ExpectedError) + } else { + require.NoError(t, err) + } actions := []string{} for _, action := range client.Actions { From d4a2787fca9373bd8fd0a8df69acd0df69b3930e Mon Sep 17 00:00:00 2001 From: sarfraj89 Date: Tue, 6 Jan 2026 23:31:18 +0530 Subject: [PATCH 124/571] Lint sync-repo.sh with ShellCheck Signed-off-by: sarfraj89 --- scripts/sync-repo.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/sync-repo.sh b/scripts/sync-repo.sh index 453102072..0143c597e 100755 --- a/scripts/sync-repo.sh +++ b/scripts/sync-repo.sh @@ -44,7 +44,7 @@ verify_prereqs() { } confirm() { - case $response in + case $confirm in [yY][eE][sS]|[yY]) true ;; @@ -66,11 +66,11 @@ main() { verify_prereqs # dry run of the command - gsutil rsync -d -n $1 gs://$2 + gsutil rsync -d -n "$1" gs://"$2" - read -p "Are you sure you would like to continue with these changes? [y/N]} " confirm + read -r -p "Are you sure you would like to continue with these changes? [y/N] " confirm if [[ $confirm =~ [yY](es)* ]]; then - gsutil rsync -d $1 gs://$2 + gsutil rsync -d "$1" gs://"$2" else error_exit "Discontinuing sync process." fi From 0e0c02e6a71097c2ac0e4d75742e6048dc83aa19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 21:37:45 +0000 Subject: [PATCH 125/571] chore(deps): bump github.com/foxcpp/go-mockdns from 1.1.0 to 1.2.0 Bumps [github.com/foxcpp/go-mockdns](https://github.com/foxcpp/go-mockdns) from 1.1.0 to 1.2.0. - [Release notes](https://github.com/foxcpp/go-mockdns/releases) - [Commits](https://github.com/foxcpp/go-mockdns/compare/v1.1.0...v1.2.0) --- updated-dependencies: - dependency-name: github.com/foxcpp/go-mockdns dependency-version: 1.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 128ee6573..8b5b2f4a3 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.18.0 github.com/fluxcd/cli-utils v0.37.0-flux.1 - github.com/foxcpp/go-mockdns v1.1.0 + github.com/foxcpp/go-mockdns v1.2.0 github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.13.0 github.com/gosuri/uitable v0.0.4 diff --git a/go.sum b/go.sum index 446081173..4e39db6d8 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fluxcd/cli-utils v0.37.0-flux.1 h1:k/VvPNT3tGa/l2N+qzHduaQr3GVbgoWS6nw7tGZz16w= github.com/fluxcd/cli-utils v0.37.0-flux.1/go.mod h1:aND5wX3LuTFtB7eUT7vsWr8mmxRVSPR2Wkvbn0SqPfw= -github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= -github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= +github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= +github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= From a333bbaf273645bf53fb873228040ca8edde849a Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Wed, 7 Jan 2026 12:51:47 +0000 Subject: [PATCH 126/571] Update pkg/chart/common/util/coalesce_test.go Co-authored-by: George Jenkins Signed-off-by: Evans Mungai --- pkg/chart/common/util/coalesce_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index a7d841bbc..789c72652 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -25,7 +25,7 @@ import ( "text/template" "github.com/stretchr/testify/assert" - req "github.com/stretchr/testify/require" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" From b8937ad1922bca47be8bbf8e6274608ebc34a778 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Wed, 7 Jan 2026 12:58:21 +0000 Subject: [PATCH 127/571] Fix import Signed-off-by: Evans Mungai --- pkg/chart/common/util/coalesce_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index 789c72652..a7d841bbc 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -25,7 +25,7 @@ import ( "text/template" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + req "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" From 0298b2ffd0823eead74c75e1b890b0bf47d0db62 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Wed, 7 Jan 2026 13:16:11 +0000 Subject: [PATCH 128/571] Remove refactorring changes from coalesce_test.go Signed-off-by: Evans Mungai --- pkg/chart/common/util/coalesce_test.go | 228 +++++++++++++------------ 1 file changed, 119 insertions(+), 109 deletions(-) diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index a7d841bbc..4eaf4be2b 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -25,7 +25,6 @@ import ( "text/template" "github.com/stretchr/testify/assert" - req "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" @@ -402,120 +401,131 @@ func TestMergeValues(t *testing.T) { } func TestCoalesceTables(t *testing.T) { - t.Run("case 1", func(t *testing.T) { - is := assert.New(t) - dst := map[string]interface{}{ - "name": "Ishmael", - "address": map[string]interface{}{ - "street": "123 Spouter Inn Ct.", - "city": "Nantucket", - "country": nil, - }, - "details": map[string]interface{}{ - "friends": []string{"Tashtego"}, - }, - "boat": "pequod", - "hole": nil, - } - src := map[string]interface{}{ - "occupation": "whaler", - "address": map[string]interface{}{ - "state": "MA", - "street": "234 Spouter Inn Ct.", - "country": "US", - }, - "details": "empty", - "boat": map[string]interface{}{ - "mast": true, - }, - "hole": "black", - } + dst := map[string]interface{}{ + "name": "Ishmael", + "address": map[string]interface{}{ + "street": "123 Spouter Inn Ct.", + "city": "Nantucket", + "country": nil, + }, + "details": map[string]interface{}{ + "friends": []string{"Tashtego"}, + }, + "boat": "pequod", + "hole": nil, + } + src := map[string]interface{}{ + "occupation": "whaler", + "address": map[string]interface{}{ + "state": "MA", + "street": "234 Spouter Inn Ct.", + "country": "US", + }, + "details": "empty", + "boat": map[string]interface{}{ + "mast": true, + }, + "hole": "black", + } - // What we expect is that anything in dst overrides anything in src, but that - // otherwise the values are coalesced. - CoalesceTables(dst, src) - - is.Equal("Ishmael", dst["name"], "Unexpected name: %s", dst["name"]) - is.Equal("whaler", dst["occupation"], "Unexpected occupation: %s", dst["occupation"]) - - addr, ok := dst["address"].(map[string]interface{}) - req.True(t, ok, "Address went away.") - - is.Equal("123 Spouter Inn Ct.", addr["street"], "Unexpected address: %v", addr["street"]) - is.Equal("Nantucket", addr["city"], "Unexpected city: %v", addr["city"]) - is.Equal("MA", addr["state"], "Unexpected state: %v", addr["state"]) - _, ok = addr["country"] - is.False(ok, "The country should be removed") - - det, ok := dst["details"].(map[string]interface{}) - req.True(t, ok, "Details is the wrong type: %v", dst["details"]) - _, ok = det["friends"] - is.True(ok, "Could not find your friends. Maybe you don't have any. :-(") - - is.Equal("pequod", dst["boat"], "Expected boat string, got %v", dst["boat"]) - _, ok = dst["hole"] - is.False(ok, "The hole should be removed") - }) - t.Run("case 2", func(_ *testing.T) { - is := assert.New(t) - dst2 := map[string]interface{}{ - "name": "Ishmael", - "address": map[string]interface{}{ - "street": "123 Spouter Inn Ct.", - "city": "Nantucket", - "country": "US", - }, - "details": map[string]interface{}{ - "friends": []string{"Tashtego"}, - }, - "boat": "pequod", - "hole": "black", - } + // What we expect is that anything in dst overrides anything in src, but that + // otherwise the values are coalesced. + CoalesceTables(dst, src) - // What we expect is that anything in dst should have all values set, - // this happens when the --reuse-values flag is set but the chart has no modifications yet - CoalesceTables(dst2, nil) - - is.Equal("Ishmael", dst2["name"], "Unexpected name: %s", dst2["name"]) - addr2, ok := dst2["address"].(map[string]interface{}) - req.True(t, ok, "Address went away.") - is.Equal("123 Spouter Inn Ct.", addr2["street"], "Unexpected address: %v", addr2["street"]) - is.Equal("Nantucket", addr2["city"], "Unexpected city: %v", addr2["city"]) - is.Equal("US", addr2["country"], "Unexpected country: %v", addr2["country"]) - is.Equal("US", addr2["country"], "Unexpected country: %v", addr2["country"]) - - det2, ok := dst2["details"].(map[string]interface{}) - req.True(t, ok, "Details is the wrong type: %v", dst2["details"]) - _, ok = det2["friends"] - is.True(ok, "Could not find your friends. Maybe you don't have any. :-(") - - is.Equal("pequod", dst2["boat"], "Expected boat string, got %v", dst2["boat"]) - is.Equal("black", dst2["hole"], "Expected hole string, got %v", dst2["hole"]) - }) - t.Run("chart values with nil user value", func(t *testing.T) { - is := assert.New(t) - dst := map[string]any{ - "foo": "bar", - "baz": nil, // explicit nil from user - } + if dst["name"] != "Ishmael" { + t.Errorf("Unexpected name: %s", dst["name"]) + } + if dst["occupation"] != "whaler" { + t.Errorf("Unexpected occupation: %s", dst["occupation"]) + } - // Chart's default values (src - lower priority) - empty map - src := map[string]any{ - "ben": nil, - } + addr, ok := dst["address"].(map[string]interface{}) + if !ok { + t.Fatal("Address went away.") + } + + if addr["street"].(string) != "123 Spouter Inn Ct." { + t.Errorf("Unexpected address: %v", addr["street"]) + } + + if addr["city"].(string) != "Nantucket" { + t.Errorf("Unexpected city: %v", addr["city"]) + } + + if addr["state"].(string) != "MA" { + t.Errorf("Unexpected state: %v", addr["state"]) + } - CoalesceTables(dst, src) + if _, ok = addr["country"]; ok { + t.Error("The country is not left out.") + } + + if det, ok := dst["details"].(map[string]interface{}); !ok { + t.Fatalf("Details is the wrong type: %v", dst["details"]) + } else if _, ok := det["friends"]; !ok { + t.Error("Could not find your friends. Maybe you don't have any. :-(") + } + + if dst["boat"].(string) != "pequod" { + t.Errorf("Expected boat string, got %v", dst["boat"]) + } + + if _, ok = dst["hole"]; ok { + t.Error("The hole still exists.") + } + + dst2 := map[string]interface{}{ + "name": "Ishmael", + "address": map[string]interface{}{ + "street": "123 Spouter Inn Ct.", + "city": "Nantucket", + "country": "US", + }, + "details": map[string]interface{}{ + "friends": []string{"Tashtego"}, + }, + "boat": "pequod", + "hole": "black", + } - // "foo" should be preserved - is.Equal("bar", dst["foo"]) - _, ok := dst["ben"] - is.True(ok, "Expected ben key to be present") - is.Nil(dst["ben"], "Expected ben key to be nil but it is not") + // What we expect is that anything in dst should have all values set, + // this happens when the --reuse-values flag is set but the chart has no modifications yet + CoalesceTables(dst2, nil) - _, ok = dst["baz"] - is.True(ok, "Expected baz key to be present but it was removed") - is.Nil(dst["baz"], "Expected baz key to be nil but it is not") - }) + if dst2["name"] != "Ishmael" { + t.Errorf("Unexpected name: %s", dst2["name"]) + } + + addr2, ok := dst2["address"].(map[string]interface{}) + if !ok { + t.Fatal("Address went away.") + } + + if addr2["street"].(string) != "123 Spouter Inn Ct." { + t.Errorf("Unexpected address: %v", addr2["street"]) + } + + if addr2["city"].(string) != "Nantucket" { + t.Errorf("Unexpected city: %v", addr2["city"]) + } + + if addr2["country"].(string) != "US" { + t.Errorf("Unexpected Country: %v", addr2["country"]) + } + + if det2, ok := dst2["details"].(map[string]interface{}); !ok { + t.Fatalf("Details is the wrong type: %v", dst2["details"]) + } else if _, ok := det2["friends"]; !ok { + t.Error("Could not find your friends. Maybe you don't have any. :-(") + } + + if dst2["boat"].(string) != "pequod" { + t.Errorf("Expected boat string, got %v", dst2["boat"]) + } + + if dst2["hole"].(string) != "black" { + t.Errorf("Expected hole string, got %v", dst2["boat"]) + } } func TestMergeTables(t *testing.T) { From 9125b84f450e350c5bafb8de2b42701532b04c29 Mon Sep 17 00:00:00 2001 From: Sarfraj Khan Date: Thu, 8 Jan 2026 17:15:00 +0530 Subject: [PATCH 129/571] Suppress SC2154 without changing behavior Signed-off-by: Sarfraj Khan --- scripts/sync-repo.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/sync-repo.sh b/scripts/sync-repo.sh index 0143c597e..0651d3634 100755 --- a/scripts/sync-repo.sh +++ b/scripts/sync-repo.sh @@ -44,7 +44,8 @@ verify_prereqs() { } confirm() { - case $confirm in + # shellcheck disable=SC2154 + case $response in [yY][eE][sS]|[yY]) true ;; From e751a70e84175212d9338738122d045aecb2ad89 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Fri, 9 Jan 2026 19:32:29 +0000 Subject: [PATCH 130/571] fix(release): fix test compilation error Signed-off-by: Evans Mungai --- pkg/action/release_testing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/action/release_testing_test.go b/pkg/action/release_testing_test.go index 1ada78a4b..bece0b475 100644 --- a/pkg/action/release_testing_test.go +++ b/pkg/action/release_testing_test.go @@ -46,7 +46,7 @@ func TestReleaseTestingRun_UnreachableKubeClient(t *testing.T) { config.KubeClient = &failingKubeClient client := NewReleaseTesting(config) - result, err := client.Run("") + result, _, err := client.Run("") assert.Nil(t, result) assert.Error(t, err) } From 46e52649eaf7702b37f38302a318171ee88f4710 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 21:37:20 +0000 Subject: [PATCH 131/571] chore(deps): bump golang.org/x/text from 0.32.0 to 0.33.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.32.0 to 0.33.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.32.0...v0.33.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.33.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 8b5b2f4a3..8b4b44af8 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.46.0 golang.org/x/term v0.38.0 - golang.org/x/text v0.32.0 + golang.org/x/text v0.33.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.35.0 k8s.io/apiextensions-apiserver v0.35.0 @@ -157,13 +157,13 @@ require ( go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.47.0 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools v0.40.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect google.golang.org/grpc v1.72.2 // indirect diff --git a/go.sum b/go.sum index 4e39db6d8..b0b582ef8 100644 --- a/go.sum +++ b/go.sum @@ -382,8 +382,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -394,8 +394,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -443,8 +443,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -453,8 +453,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= From 561410ae1d09c2aa289ff8d8cad5b7fa979cd135 Mon Sep 17 00:00:00 2001 From: Manuel Alonso Gonzalez Date: Sat, 10 Jan 2026 09:49:30 +0100 Subject: [PATCH 132/571] fix(test): merge fix correctly Signed-off-by: Manuel Alonso Gonzalez <434575+manute@users.noreply.github.com> Signed-off-by: Manuel Alonso Gonzalez --- pkg/action/install_test.go | 113 +++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 8aa243663..fc8a96319 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -1074,6 +1074,119 @@ func TestInstallRun_UnreachableKubeClient(t *testing.T) { assert.ErrorContains(t, err, "connection refused") } +func TestInstallSetRegistryClient(t *testing.T) { + config := actionConfigFixture(t) + instAction := NewInstall(config) + + registryClient := ®istry.Client{} + instAction.SetRegistryClient(registryClient) + + assert.Equal(t, registryClient, instAction.GetRegistryClient()) +} + +func TestInstalLCRDs(t *testing.T) { + config := actionConfigFixture(t) + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + assert.Len(t, crdsToInstall, 1) + assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) + + require.NoError(t, instAction.installCRDs(crdsToInstall)) +} + +func TestInstalLCRDs_KubeClient_BuildError(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.BuildError = errors.New("build error") + config.KubeClient = &failingKubeClient + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + + require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") +} + +func TestInstalLCRDs_KubeClient_CreateError(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.CreateError = errors.New("create error") + config.KubeClient = &failingKubeClient + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + + require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") +} + +func TestInstalLCRDs_AlreadyExist(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + mockError := &apierrors.StatusError{ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Reason: metav1.StatusReasonAlreadyExists, + }} + failingKubeClient.CreateError = mockError + config.KubeClient = &failingKubeClient + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + + assert.Nil(t, instAction.installCRDs(crdsToInstall)) +} + +func TestInstalLCRDs_WaiterError(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.WaitError = errors.New("wait error") + failingKubeClient.BuildDummy = true + config.KubeClient = &failingKubeClient + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + + require.Error(t, instAction.installCRDs(crdsToInstall), "wait error") +} + +func TestCheckDependencies(t *testing.T) { + dependency := chart.Dependency{Name: "hello"} + mockChart := buildChart(withDependency()) + + assert.Nil(t, CheckDependencies(mockChart, []ci.Dependency{&dependency})) +} + +func TestCheckDependencies_MissingDependency(t *testing.T) { + dependency := chart.Dependency{Name: "missing"} + mockChart := buildChart(withDependency()) + + assert.ErrorContains(t, CheckDependencies(mockChart, []ci.Dependency{&dependency}), "missing in charts") +} + func TestInstallCRDs_CheckNilErrors(t *testing.T) { tests := []struct { name string From de0becdc57de8e39f44d2b0f645eba2e3272ad5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 10 Jan 2026 10:33:37 +0000 Subject: [PATCH 133/571] chore(deps): bump golang.org/x/term from 0.38.0 to 0.39.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.38.0 to 0.39.0. - [Commits](https://github.com/golang/term/compare/v0.38.0...v0.39.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-version: 0.39.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 8b4b44af8..81162118a 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/tetratelabs/wazero v1.11.0 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.46.0 - golang.org/x/term v0.38.0 + golang.org/x/term v0.39.0 golang.org/x/text v0.33.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.35.0 @@ -161,7 +161,7 @@ require ( golang.org/x/net v0.48.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.39.0 // indirect + golang.org/x/sys v0.40.0 // indirect golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.40.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect diff --git a/go.sum b/go.sum index b0b582ef8..abeb76478 100644 --- a/go.sum +++ b/go.sum @@ -425,8 +425,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -434,8 +434,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= From 0f949a92c149cf11e5bb19caf4d19d05567be6eb Mon Sep 17 00:00:00 2001 From: Manuel Alonso Date: Sun, 11 Jan 2026 21:08:31 +0100 Subject: [PATCH 134/571] fix(test): fix tests and check nil for restclient Signed-off-by: Manuel Alonso --- pkg/action/install.go | 48 ++++++++++++++++++++------------------ pkg/action/install_test.go | 15 ++++++------ 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index f1677c297..b84e9eaf9 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -222,32 +222,34 @@ func (i *Install) installCRDs(crds []chart.CRD) error { return err } - // If we have already gathered the capabilities, we need to invalidate - // the cache so that the new CRDs are recognized. This should only be - // the case when an action configuration is reused for multiple actions, - // as otherwise it is later loaded by ourselves when getCapabilities - // is called later on in the installation process. - if i.cfg.Capabilities != nil { - discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() - if err != nil { - return err - } + if i.cfg.RESTClientGetter != nil { + // If we have already gathered the capabilities, we need to invalidate + // the cache so that the new CRDs are recognized. This should only be + // the case when an action configuration is reused for multiple actions, + // as otherwise it is later loaded by ourselves when getCapabilities + // is called later on in the installation process. + if i.cfg.Capabilities != nil { + discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() + if err != nil { + return err + } - i.cfg.Logger().Debug("clearing discovery cache") - discoveryClient.Invalidate() + i.cfg.Logger().Debug("clearing discovery cache") + discoveryClient.Invalidate() - _, _ = discoveryClient.ServerGroups() - } + _, _ = discoveryClient.ServerGroups() + } - // Invalidate the REST mapper, since it will not have the new CRDs - // present. - restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper() - if err != nil { - return err - } - if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { - i.cfg.Logger().Debug("clearing REST mapper cache") - resettable.Reset() + // Invalidate the REST mapper, since it will not have the new CRDs + // present. + restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper() + if err != nil { + return err + } + if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { + i.cfg.Logger().Debug("clearing REST mapper cache") + resettable.Reset() + } } } return nil diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index fc8a96319..76c35b628 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -1084,8 +1084,8 @@ func TestInstallSetRegistryClient(t *testing.T) { assert.Equal(t, registryClient, instAction.GetRegistryClient()) } -func TestInstalLCRDs(t *testing.T) { - config := actionConfigFixture(t) +func TestInstallCRDs(t *testing.T) { + config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false)) instAction := NewInstall(config) mockFile := common.File{ @@ -1100,7 +1100,7 @@ func TestInstalLCRDs(t *testing.T) { require.NoError(t, instAction.installCRDs(crdsToInstall)) } -func TestInstalLCRDs_KubeClient_BuildError(t *testing.T) { +func TestInstallCRDs_KubeClient_BuildError(t *testing.T) { config := actionConfigFixture(t) failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} failingKubeClient.BuildError = errors.New("build error") @@ -1117,7 +1117,7 @@ func TestInstalLCRDs_KubeClient_BuildError(t *testing.T) { require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") } -func TestInstalLCRDs_KubeClient_CreateError(t *testing.T) { +func TestInstallCRDs_KubeClient_CreateError(t *testing.T) { config := actionConfigFixture(t) failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} failingKubeClient.CreateError = errors.New("create error") @@ -1134,7 +1134,7 @@ func TestInstalLCRDs_KubeClient_CreateError(t *testing.T) { require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") } -func TestInstalLCRDs_AlreadyExist(t *testing.T) { +func TestInstallCRDs_AlreadyExist(t *testing.T) { config := actionConfigFixture(t) failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} mockError := &apierrors.StatusError{ErrStatus: metav1.Status{ @@ -1149,13 +1149,14 @@ func TestInstalLCRDs_AlreadyExist(t *testing.T) { Name: "crds/foo.yaml", Data: []byte("hello"), } + mockChart := buildChart(withFile(mockFile)) crdsToInstall := mockChart.CRDObjects() - assert.Nil(t, instAction.installCRDs(crdsToInstall)) + require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") } -func TestInstalLCRDs_WaiterError(t *testing.T) { +func TestInstallCRDs_WaiterError(t *testing.T) { config := actionConfigFixture(t) failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} failingKubeClient.WaitError = errors.New("wait error") From 59ece92bed72767332ddc827c5548e7355ffb09a Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Fri, 9 Jan 2026 01:18:33 +0000 Subject: [PATCH 135/571] pkg/kube: introduce support for custom kstatus readers Signed-off-by: Matheus Pimenta --- pkg/action/hooks.go | 44 +++-- pkg/action/hooks_test.go | 55 +++++- pkg/action/install.go | 21 +- pkg/action/install_test.go | 22 +++ pkg/action/release_testing.go | 7 +- pkg/action/release_testing_test.go | 28 +++ pkg/action/rollback.go | 12 +- pkg/action/rollback_test.go | 40 ++++ pkg/action/uninstall.go | 13 +- pkg/action/uninstall_test.go | 38 ++++ pkg/action/upgrade.go | 14 +- pkg/action/upgrade_test.go | 27 +++ pkg/kube/client.go | 31 ++- pkg/kube/client_test.go | 123 ++++++++++-- pkg/kube/fake/failing_kube_client.go | 10 +- pkg/kube/fake/printer.go | 6 +- pkg/kube/interface.go | 13 +- pkg/kube/options.go | 45 +++++ pkg/kube/statuswait.go | 15 +- pkg/kube/statuswait_test.go | 277 ++++++++++++++++++++++++++- 20 files changed, 782 insertions(+), 59 deletions(-) create mode 100644 pkg/kube/options.go diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index 49849a27d..28033395b 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -32,8 +32,11 @@ import ( ) // execHook executes all of the hooks for the given hook event. -func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) error { - shutdown, err := cfg.execHookWithDelayedShutdown(rl, hook, waitStrategy, timeout, serverSideApply) +func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, + waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, + timeout time.Duration, serverSideApply bool) error { + + shutdown, err := cfg.execHookWithDelayedShutdown(rl, hook, waitStrategy, waitOptions, timeout, serverSideApply) if shutdown == nil { return err } @@ -53,7 +56,10 @@ func shutdownNoOp() error { } // execHookWithDelayedShutdown executes all of the hooks for the given hook event and returns a shutdownHook function to trigger deletions after doing other things like e.g. retrieving logs. -func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, timeout time.Duration, serverSideApply bool) (ExecuteShutdownFunc, error) { +func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, + waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration, + serverSideApply bool) (ExecuteShutdownFunc, error) { + executingHooks := []*release.Hook{} for _, h := range rl.Hooks { @@ -71,7 +77,7 @@ func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook // Set default delete policy to before-hook-creation cfg.hookSetDeletePolicy(h) - if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, waitStrategy, timeout); err != nil { + if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, waitStrategy, waitOptions, timeout); err != nil { return shutdownNoOp, err } @@ -101,7 +107,12 @@ func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook return shutdownNoOp, fmt.Errorf("warning: Hook %s %s failed: %w", hook, h.Path, err) } - waiter, err := cfg.KubeClient.GetWaiter(waitStrategy) + var waiter kube.Waiter + if c, supportsOptions := cfg.KubeClient.(kube.InterfaceWaitOptions); supportsOptions { + waiter, err = c.GetWaiterWithOptions(waitStrategy, waitOptions...) + } else { + waiter, err = cfg.KubeClient.GetWaiter(waitStrategy) + } if err != nil { return shutdownNoOp, fmt.Errorf("unable to get waiter: %w", err) } @@ -120,14 +131,14 @@ func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook // If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted // under failed condition. If so, then clear the corresponding resource object in the hook return func() error { - if errDeleting := cfg.deleteHookByPolicy(h, release.HookFailed, waitStrategy, timeout); errDeleting != nil { + if errDeleting := cfg.deleteHookByPolicy(h, release.HookFailed, waitStrategy, waitOptions, timeout); errDeleting != nil { // We log the error here as we want to propagate the hook failure upwards to the release object. log.Printf("error deleting the hook resource on hook failure: %v", errDeleting) } // If a hook is failed, check the annotation of the previous successful hooks to determine whether the hooks // should be deleted under succeeded condition. - if err := cfg.deleteHooksByPolicy(executingHooks[0:i], release.HookSucceeded, waitStrategy, timeout); err != nil { + if err := cfg.deleteHooksByPolicy(executingHooks[0:i], release.HookSucceeded, waitStrategy, waitOptions, timeout); err != nil { return err } return err @@ -145,7 +156,7 @@ func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook // We log here as we still want to attempt hook resource deletion even if output logging fails. log.Printf("error outputting logs for hook failure: %v", err) } - if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, waitStrategy, timeout); err != nil { + if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, waitStrategy, waitOptions, timeout); err != nil { return err } } @@ -166,7 +177,9 @@ func (x hookByWeight) Less(i, j int) bool { } // deleteHookByPolicy deletes a hook if the hook policy instructs it to -func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy, waitStrategy kube.WaitStrategy, timeout time.Duration) error { +func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy, + waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error { + // Never delete CustomResourceDefinitions; this could cause lots of // cascading garbage collection. if h.Kind == "CustomResourceDefinition" { @@ -182,7 +195,12 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo return joinErrors(errs, "; ") } - waiter, err := cfg.KubeClient.GetWaiter(waitStrategy) + var waiter kube.Waiter + if c, supportsOptions := cfg.KubeClient.(kube.InterfaceWaitOptions); supportsOptions { + waiter, err = c.GetWaiterWithOptions(waitStrategy, waitOptions...) + } else { + waiter, err = cfg.KubeClient.GetWaiter(waitStrategy) + } if err != nil { return err } @@ -194,9 +212,11 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo } // deleteHooksByPolicy deletes all hooks if the hook policy instructs it to -func (cfg *Configuration) deleteHooksByPolicy(hooks []*release.Hook, policy release.HookDeletePolicy, waitStrategy kube.WaitStrategy, timeout time.Duration) error { +func (cfg *Configuration) deleteHooksByPolicy(hooks []*release.Hook, policy release.HookDeletePolicy, + waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error { + for _, h := range hooks { - if err := cfg.deleteHookByPolicy(h, policy, waitStrategy, timeout); err != nil { + if err := cfg.deleteHookByPolicy(h, policy, waitStrategy, waitOptions, timeout); err != nil { return err } } diff --git a/pkg/action/hooks_test.go b/pkg/action/hooks_test.go index 02b70dda1..0270a0630 100644 --- a/pkg/action/hooks_test.go +++ b/pkg/action/hooks_test.go @@ -18,6 +18,7 @@ package action import ( "bytes" + "context" "fmt" "io" "reflect" @@ -278,8 +279,8 @@ func (h *HookFailingKubeClient) Delete(resources kube.ResourceList, deletionProp return h.PrintingKubeClient.Delete(resources, deletionPropagation) } -func (h *HookFailingKubeClient) GetWaiter(strategy kube.WaitStrategy) (kube.Waiter, error) { - waiter, _ := h.PrintingKubeClient.GetWaiter(strategy) +func (h *HookFailingKubeClient) GetWaiterWithOptions(strategy kube.WaitStrategy, opts ...kube.WaitOption) (kube.Waiter, error) { + waiter, _ := h.PrintingKubeClient.GetWaiterWithOptions(strategy, opts...) return &HookFailingKubeWaiter{ PrintingKubeWaiter: waiter.(*kubefake.PrintingKubeWaiter), failOn: h.failOn, @@ -394,7 +395,7 @@ data: } serverSideApply := true - err := configuration.execHook(&tc.inputRelease, hookEvent, kube.StatusWatcherStrategy, 600, serverSideApply) + err := configuration.execHook(&tc.inputRelease, hookEvent, kube.StatusWatcherStrategy, nil, 600, serverSideApply) if !reflect.DeepEqual(kubeClient.deleteRecord, tc.expectedDeleteRecord) { t.Fatalf("Got unexpected delete record, expected: %#v, but got: %#v", kubeClient.deleteRecord, tc.expectedDeleteRecord) @@ -442,3 +443,51 @@ func TestConfiguration_hookSetDeletePolicy(t *testing.T) { }) } } + +func TestExecHook_WaitOptionsPassedDownstream(t *testing.T) { + is := assert.New(t) + + failer := &kubefake.FailingKubeClient{ + PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, + } + + configuration := &Configuration{ + Releases: storage.Init(driver.NewMemory()), + KubeClient: failer, + Capabilities: common.DefaultCapabilities, + } + + rel := &release.Release{ + Name: "test-release", + Namespace: "test", + Hooks: []*release.Hook{ + { + Name: "test-hook", + Kind: "ConfigMap", + Path: "templates/hook.yaml", + Manifest: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test-hook + namespace: test +data: + foo: bar +`, + Weight: 0, + Events: []release.HookEvent{ + release.HookPreInstall, + }, + }, + }, + } + + // Use WithWaitContext as a marker WaitOption that we can track + ctx := context.Background() + waitOptions := []kube.WaitOption{kube.WithWaitContext(ctx)} + + err := configuration.execHook(rel, release.HookPreInstall, kube.StatusWatcherStrategy, waitOptions, 600, false) + is.NoError(err) + + // Verify that WaitOptions were passed to GetWaiter + is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") +} diff --git a/pkg/action/install.go b/pkg/action/install.go index b379d6873..38355491a 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -95,6 +95,7 @@ type Install struct { DisableHooks bool Replace bool WaitStrategy kube.WaitStrategy + WaitOptions []kube.WaitOption WaitForJobs bool Devel bool DependencyUpdate bool @@ -201,7 +202,13 @@ func (i *Install) installCRDs(crds []chart.CRD) error { totalItems = append(totalItems, res...) } if len(totalItems) > 0 { - waiter, err := i.cfg.KubeClient.GetWaiter(i.WaitStrategy) + var waiter kube.Waiter + var err error + if c, supportsOptions := i.cfg.KubeClient.(kube.InterfaceWaitOptions); supportsOptions { + waiter, err = c.GetWaiterWithOptions(i.WaitStrategy, i.WaitOptions...) + } else { + waiter, err = i.cfg.KubeClient.GetWaiter(i.WaitStrategy) + } if err != nil { return fmt.Errorf("unable to get waiter: %w", err) } @@ -480,7 +487,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource var err error // pre-install hooks if !i.DisableHooks { - if err := i.cfg.execHook(rel, release.HookPreInstall, i.WaitStrategy, i.Timeout, i.ServerSideApply); err != nil { + if err := i.cfg.execHook(rel, release.HookPreInstall, i.WaitStrategy, i.WaitOptions, i.Timeout, i.ServerSideApply); err != nil { return rel, fmt.Errorf("failed pre-install: %s", err) } } @@ -506,7 +513,12 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource return rel, err } - waiter, err := i.cfg.KubeClient.GetWaiter(i.WaitStrategy) + var waiter kube.Waiter + if c, supportsOptions := i.cfg.KubeClient.(kube.InterfaceWaitOptions); supportsOptions { + waiter, err = c.GetWaiterWithOptions(i.WaitStrategy, i.WaitOptions...) + } else { + waiter, err = i.cfg.KubeClient.GetWaiter(i.WaitStrategy) + } if err != nil { return rel, fmt.Errorf("failed to get waiter: %w", err) } @@ -521,7 +533,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource } if !i.DisableHooks { - if err := i.cfg.execHook(rel, release.HookPostInstall, i.WaitStrategy, i.Timeout, i.ServerSideApply); err != nil { + if err := i.cfg.execHook(rel, release.HookPostInstall, i.WaitStrategy, i.WaitOptions, i.Timeout, i.ServerSideApply); err != nil { return rel, fmt.Errorf("failed post-install: %s", err) } } @@ -555,6 +567,7 @@ func (i *Install) failRelease(rel *release.Release, err error) (*release.Release uninstall.KeepHistory = false uninstall.Timeout = i.Timeout uninstall.WaitStrategy = i.WaitStrategy + uninstall.WaitOptions = i.WaitOptions if _, uninstallErr := uninstall.Run(i.ReleaseName); uninstallErr != nil { return rel, fmt.Errorf("an error occurred while uninstalling the release. original install error: %w: %w", err, uninstallErr) } diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 9a0ca8d22..47080aef8 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -1186,3 +1186,25 @@ func TestCheckDependencies_MissingDependency(t *testing.T) { assert.ErrorContains(t, CheckDependencies(mockChart, []ci.Dependency{&dependency}), "missing in charts") } + +func TestInstallRelease_WaitOptionsPassedDownstream(t *testing.T) { + is := assert.New(t) + + instAction := installAction(t) + instAction.ReleaseName = "wait-options-test" + instAction.WaitStrategy = kube.StatusWatcherStrategy + + // Use WithWaitContext as a marker WaitOption that we can track + ctx := context.Background() + instAction.WaitOptions = []kube.WaitOption{kube.WithWaitContext(ctx)} + + // Access the underlying FailingKubeClient to check recorded options + failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) + + vals := map[string]interface{}{} + _, err := instAction.Run(buildChart(), vals) + is.NoError(err) + + // Verify that WaitOptions were passed to GetWaiter + is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") +} diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index 992cdd701..043a41236 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -41,8 +41,9 @@ const ( // // It provides the implementation of 'helm test'. type ReleaseTesting struct { - cfg *Configuration - Timeout time.Duration + cfg *Configuration + Timeout time.Duration + WaitOptions []kube.WaitOption // Used for fetching logs from test pods Namespace string Filters map[string][]string @@ -102,7 +103,7 @@ func (r *ReleaseTesting) Run(name string) (ri.Releaser, ExecuteShutdownFunc, err } serverSideApply := rel.ApplyMethod == string(release.ApplyMethodServerSideApply) - shutdown, err := r.cfg.execHookWithDelayedShutdown(rel, release.HookTest, kube.StatusWatcherStrategy, r.Timeout, serverSideApply) + shutdown, err := r.cfg.execHookWithDelayedShutdown(rel, release.HookTest, kube.StatusWatcherStrategy, r.WaitOptions, r.Timeout, serverSideApply) if err != nil { rel.Hooks = append(skippedHooks, rel.Hooks...) diff --git a/pkg/action/release_testing_test.go b/pkg/action/release_testing_test.go index bece0b475..ab35e104a 100644 --- a/pkg/action/release_testing_test.go +++ b/pkg/action/release_testing_test.go @@ -18,6 +18,7 @@ package action import ( "bytes" + "context" "errors" "io" "os" @@ -27,6 +28,7 @@ import ( "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/kube" kubefake "helm.sh/helm/v4/pkg/kube/fake" release "helm.sh/helm/v4/pkg/release/v1" ) @@ -89,3 +91,29 @@ func TestReleaseTestingGetPodLogs_PodRetrievalError(t *testing.T) { require.ErrorContains(t, client.GetPodLogs(&bytes.Buffer{}, &release.Release{Hooks: hooks}), "unable to get pod logs") } + +func TestReleaseTesting_WaitOptionsPassedDownstream(t *testing.T) { + is := assert.New(t) + config := actionConfigFixture(t) + + // Create a release with a test hook + rel := releaseStub() + rel.Name = "wait-options-test-release" + rel.ApplyMethod = "csa" + require.NoError(t, config.Releases.Create(rel)) + + client := NewReleaseTesting(config) + + // Use WithWaitContext as a marker WaitOption that we can track + ctx := context.Background() + client.WaitOptions = []kube.WaitOption{kube.WithWaitContext(ctx)} + + // Access the underlying FailingKubeClient to check recorded options + failer := config.KubeClient.(*kubefake.FailingKubeClient) + + _, _, err := client.Run(rel.Name) + is.NoError(err) + + // Verify that WaitOptions were passed to GetWaiter + is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") +} diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 4cdb2d33b..03150532e 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -40,6 +40,7 @@ type Rollback struct { Version int Timeout time.Duration WaitStrategy kube.WaitStrategy + WaitOptions []kube.WaitOption WaitForJobs bool DisableHooks bool // DryRunStrategy can be set to prepare, but not execute the operation and whether or not to interact with the remote cluster @@ -210,7 +211,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas // pre-rollback hooks if !r.DisableHooks { - if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.WaitStrategy, r.Timeout, serverSideApply); err != nil { + if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.WaitStrategy, r.WaitOptions, r.Timeout, serverSideApply); err != nil { return targetRelease, err } } else { @@ -251,7 +252,12 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas return targetRelease, err } - waiter, err := r.cfg.KubeClient.GetWaiter(r.WaitStrategy) + var waiter kube.Waiter + if c, supportsOptions := r.cfg.KubeClient.(kube.InterfaceWaitOptions); supportsOptions { + waiter, err = c.GetWaiterWithOptions(r.WaitStrategy, r.WaitOptions...) + } else { + waiter, err = r.cfg.KubeClient.GetWaiter(r.WaitStrategy) + } if err != nil { return nil, fmt.Errorf("unable to get waiter: %w", err) } @@ -273,7 +279,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas // post-rollback hooks if !r.DisableHooks { - if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.WaitStrategy, r.Timeout, serverSideApply); err != nil { + if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.WaitStrategy, r.WaitOptions, r.Timeout, serverSideApply); err != nil { return targetRelease, err } } diff --git a/pkg/action/rollback_test.go b/pkg/action/rollback_test.go index 5158bee26..deb6c7c80 100644 --- a/pkg/action/rollback_test.go +++ b/pkg/action/rollback_test.go @@ -17,12 +17,15 @@ limitations under the License. package action import ( + "context" "errors" "io" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/kube" kubefake "helm.sh/helm/v4/pkg/kube/fake" ) @@ -43,3 +46,40 @@ func TestRollbackRun_UnreachableKubeClient(t *testing.T) { client := NewRollback(config) assert.Error(t, client.Run("")) } + +func TestRollback_WaitOptionsPassedDownstream(t *testing.T) { + is := assert.New(t) + config := actionConfigFixture(t) + + // Create a deployed release and a second version to roll back to + rel := releaseStub() + rel.Name = "wait-options-rollback" + rel.Info.Status = "deployed" + rel.ApplyMethod = "csa" + require.NoError(t, config.Releases.Create(rel)) + + rel2 := releaseStub() + rel2.Name = "wait-options-rollback" + rel2.Version = 2 + rel2.Info.Status = "deployed" + rel2.ApplyMethod = "csa" + require.NoError(t, config.Releases.Create(rel2)) + + client := NewRollback(config) + client.Version = 1 + client.WaitStrategy = kube.StatusWatcherStrategy + client.ServerSideApply = "auto" + + // Use WithWaitContext as a marker WaitOption that we can track + ctx := context.Background() + client.WaitOptions = []kube.WaitOption{kube.WithWaitContext(ctx)} + + // Access the underlying FailingKubeClient to check recorded options + failer := config.KubeClient.(*kubefake.FailingKubeClient) + + err := client.Run(rel.Name) + is.NoError(err) + + // Verify that WaitOptions were passed to GetWaiter + is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") +} diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index efbc72fef..79156991c 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -45,6 +45,7 @@ type Uninstall struct { IgnoreNotFound bool KeepHistory bool WaitStrategy kube.WaitStrategy + WaitOptions []kube.WaitOption DeletionPropagation string Timeout time.Duration Description string @@ -63,7 +64,13 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error) return nil, err } - waiter, err := u.cfg.KubeClient.GetWaiter(u.WaitStrategy) + var waiter kube.Waiter + var err error + if c, supportsOptions := u.cfg.KubeClient.(kube.InterfaceWaitOptions); supportsOptions { + waiter, err = c.GetWaiterWithOptions(u.WaitStrategy, u.WaitOptions...) + } else { + waiter, err = u.cfg.KubeClient.GetWaiter(u.WaitStrategy) + } if err != nil { return nil, err } @@ -127,7 +134,7 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error) if !u.DisableHooks { serverSideApply := true - if err := u.cfg.execHook(rel, release.HookPreDelete, u.WaitStrategy, u.Timeout, serverSideApply); err != nil { + if err := u.cfg.execHook(rel, release.HookPreDelete, u.WaitStrategy, u.WaitOptions, u.Timeout, serverSideApply); err != nil { return res, err } } else { @@ -157,7 +164,7 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error) if !u.DisableHooks { serverSideApply := true - if err := u.cfg.execHook(rel, release.HookPostDelete, u.WaitStrategy, u.Timeout, serverSideApply); err != nil { + if err := u.cfg.execHook(rel, release.HookPostDelete, u.WaitStrategy, u.WaitOptions, u.Timeout, serverSideApply); err != nil { errs = append(errs, err) } } diff --git a/pkg/action/uninstall_test.go b/pkg/action/uninstall_test.go index 6c4ad977f..b5a76d983 100644 --- a/pkg/action/uninstall_test.go +++ b/pkg/action/uninstall_test.go @@ -17,6 +17,7 @@ limitations under the License. package action import ( + "context" "errors" "fmt" "io" @@ -169,3 +170,40 @@ func TestUninstallRun_UnreachableKubeClient(t *testing.T) { assert.Nil(t, result) assert.ErrorContains(t, err, "connection refused") } + +func TestUninstall_WaitOptionsPassedDownstream(t *testing.T) { + is := assert.New(t) + + unAction := uninstallAction(t) + unAction.DisableHooks = true + unAction.DryRun = false + unAction.WaitStrategy = kube.StatusWatcherStrategy + + // Use WithWaitContext as a marker WaitOption that we can track + ctx := context.Background() + unAction.WaitOptions = []kube.WaitOption{kube.WithWaitContext(ctx)} + + rel := releaseStub() + rel.Name = "wait-options-uninstall" + rel.Manifest = `{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "secret" + }, + "type": "Opaque", + "data": { + "password": "password" + } + }` + require.NoError(t, unAction.cfg.Releases.Create(rel)) + + // Access the underlying FailingKubeClient to check recorded options + failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient) + + _, err := unAction.Run(rel.Name) + is.NoError(err) + + // Verify that WaitOptions were passed to GetWaiter + is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") +} diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 13d28fd4d..4b99be603 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -72,6 +72,8 @@ type Upgrade struct { Timeout time.Duration // WaitStrategy determines what type of waiting should be done WaitStrategy kube.WaitStrategy + // WaitOptions are additional options for waiting on resources + WaitOptions []kube.WaitOption // WaitForJobs determines whether the wait operation for the Jobs should be performed after the upgrade is requested. WaitForJobs bool // DisableHooks disables hook processing if set to true. @@ -452,7 +454,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele // pre-upgrade hooks if !u.DisableHooks { - if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.WaitStrategy, u.Timeout, serverSideApply); err != nil { + if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.WaitStrategy, u.WaitOptions, u.Timeout, serverSideApply); err != nil { u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %s", err)) return } @@ -473,7 +475,12 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele return } - waiter, err := u.cfg.KubeClient.GetWaiter(u.WaitStrategy) + var waiter kube.Waiter + if c, supportsOptions := u.cfg.KubeClient.(kube.InterfaceWaitOptions); supportsOptions { + waiter, err = c.GetWaiterWithOptions(u.WaitStrategy, u.WaitOptions...) + } else { + waiter, err = u.cfg.KubeClient.GetWaiter(u.WaitStrategy) + } if err != nil { u.cfg.recordRelease(originalRelease) u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) @@ -495,7 +502,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele // post-upgrade hooks if !u.DisableHooks { - if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.WaitStrategy, u.Timeout, serverSideApply); err != nil { + if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.WaitStrategy, u.WaitOptions, u.Timeout, serverSideApply); err != nil { u.reportToPerformUpgrade(c, upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err)) return } @@ -570,6 +577,7 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e rollin := NewRollback(u.cfg) rollin.Version = filteredHistory[0].Version rollin.WaitStrategy = u.WaitStrategy + rollin.WaitOptions = u.WaitOptions rollin.WaitForJobs = u.WaitForJobs rollin.DisableHooks = u.DisableHooks rollin.ForceReplace = u.ForceReplace diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 17c4927cc..848e8a682 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -775,3 +775,30 @@ func TestObjectKey(t *testing.T) { assert.Equal(t, "apps/v1/Deployment/namespace/name", objectKey(&info)) } + +func TestUpgradeRelease_WaitOptionsPassedDownstream(t *testing.T) { + is := assert.New(t) + req := require.New(t) + + upAction := upgradeAction(t) + rel := releaseStub() + rel.Name = "wait-options-test" + rel.Info.Status = common.StatusDeployed + req.NoError(upAction.cfg.Releases.Create(rel)) + + upAction.WaitStrategy = kube.StatusWatcherStrategy + + // Use WithWaitContext as a marker WaitOption that we can track + ctx := context.Background() + upAction.WaitOptions = []kube.WaitOption{kube.WithWaitContext(ctx)} + + // Access the underlying FailingKubeClient to check recorded options + failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) + + vals := map[string]interface{}{} + _, err := upAction.Run(rel.Name, buildChart(), vals) + req.NoError(err) + + // Verify that WaitOptions were passed to GetWaiter + is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") +} diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 6e09fceac..2fa855c1e 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -87,6 +87,8 @@ type Client struct { // WaitContext is an optional context to use for wait operations. // If not set, a context will be created internally using the // timeout provided to the wait functions. + // + // Deprecated: Use WithWaitContext wait option when getting a Waiter instead. WaitContext context.Context Waiter @@ -139,7 +141,11 @@ func init() { } } -func (c *Client) newStatusWatcher() (*statusWaiter, error) { +func (c *Client) newStatusWatcher(opts ...WaitOption) (*statusWaiter, error) { + var o waitOptions + for _, opt := range opts { + opt(&o) + } cfg, err := c.Factory.ToRESTConfig() if err != nil { return nil, err @@ -156,14 +162,23 @@ func (c *Client) newStatusWatcher() (*statusWaiter, error) { if err != nil { return nil, err } + waitContext := o.ctx + if waitContext == nil { + waitContext = c.WaitContext + } return &statusWaiter{ restMapper: restMapper, client: dynamicClient, - ctx: c.WaitContext, + ctx: waitContext, + readers: o.statusReaders, }, nil } -func (c *Client) GetWaiter(strategy WaitStrategy) (Waiter, error) { +func (c *Client) GetWaiter(ws WaitStrategy) (Waiter, error) { + return c.GetWaiterWithOptions(ws) +} + +func (c *Client) GetWaiterWithOptions(strategy WaitStrategy, opts ...WaitOption) (Waiter, error) { switch strategy { case LegacyStrategy: kc, err := c.Factory.KubernetesClientSet() @@ -172,9 +187,9 @@ func (c *Client) GetWaiter(strategy WaitStrategy) (Waiter, error) { } return &legacyWaiter{kubeClient: kc, ctx: c.WaitContext}, nil case StatusWatcherStrategy: - return c.newStatusWatcher() + return c.newStatusWatcher(opts...) case HookOnlyStrategy: - sw, err := c.newStatusWatcher() + sw, err := c.newStatusWatcher(opts...) if err != nil { return nil, err } @@ -187,8 +202,12 @@ func (c *Client) GetWaiter(strategy WaitStrategy) (Waiter, error) { } func (c *Client) SetWaiter(ws WaitStrategy) error { + return c.SetWaiterWithOptions(ws) +} + +func (c *Client) SetWaiterWithOptions(ws WaitStrategy, opts ...WaitOption) error { var err error - c.Waiter, err = c.GetWaiter(ws) + c.Waiter, err = c.GetWaiterWithOptions(ws, opts...) if err != nil { return err } diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index d442864f8..fcfd16ee7 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -28,6 +28,10 @@ import ( "testing" "time" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -722,7 +726,7 @@ func TestWait(t *testing.T) { }), } var err error - c.Waiter, err = c.GetWaiter(LegacyStrategy) + c.Waiter, err = c.GetWaiterWithOptions(LegacyStrategy) if err != nil { t.Fatal(err) } @@ -783,7 +787,7 @@ func TestWaitJob(t *testing.T) { }), } var err error - c.Waiter, err = c.GetWaiter(LegacyStrategy) + c.Waiter, err = c.GetWaiterWithOptions(LegacyStrategy) if err != nil { t.Fatal(err) } @@ -845,7 +849,7 @@ func TestWaitDelete(t *testing.T) { }), } var err error - c.Waiter, err = c.GetWaiter(LegacyStrategy) + c.Waiter, err = c.GetWaiterWithOptions(LegacyStrategy) if err != nil { t.Fatal(err) } @@ -1852,7 +1856,7 @@ func TestClientWaitContextCancellationLegacy(t *testing.T) { } var err error - c.Waiter, err = c.GetWaiter(LegacyStrategy) + c.Waiter, err = c.GetWaiterWithOptions(LegacyStrategy) require.NoError(t, err) resources, err := c.Build(objBody(&podList), false) @@ -1907,7 +1911,7 @@ func TestClientWaitWithJobsContextCancellationLegacy(t *testing.T) { } var err error - c.Waiter, err = c.GetWaiter(LegacyStrategy) + c.Waiter, err = c.GetWaiterWithOptions(LegacyStrategy) require.NoError(t, err) resources, err := c.Build(objBody(job), false) @@ -1968,7 +1972,7 @@ func TestClientWaitForDeleteContextCancellationLegacy(t *testing.T) { } var err error - c.Waiter, err = c.GetWaiter(LegacyStrategy) + c.Waiter, err = c.GetWaiterWithOptions(LegacyStrategy) require.NoError(t, err) resources, err := c.Build(objBody(&pod), false) @@ -2030,7 +2034,7 @@ func TestClientWaitContextNilDoesNotPanic(t *testing.T) { } var err error - c.Waiter, err = c.GetWaiter(LegacyStrategy) + c.Waiter, err = c.GetWaiterWithOptions(LegacyStrategy) require.NoError(t, err) resources, err := c.Build(objBody(&podList), false) @@ -2080,7 +2084,7 @@ func TestClientWaitContextPreCancelledLegacy(t *testing.T) { } var err error - c.Waiter, err = c.GetWaiter(LegacyStrategy) + c.Waiter, err = c.GetWaiterWithOptions(LegacyStrategy) require.NoError(t, err) resources, err := c.Build(objBody(&podList), false) @@ -2111,7 +2115,7 @@ metadata: namespace: default ` var err error - c.Waiter, err = c.GetWaiter(StatusWatcherStrategy) + c.Waiter, err = c.GetWaiterWithOptions(StatusWatcherStrategy) require.NoError(t, err) resources, err := c.Build(strings.NewReader(podManifest), false) @@ -2138,7 +2142,7 @@ metadata: namespace: default ` var err error - c.Waiter, err = c.GetWaiter(StatusWatcherStrategy) + c.Waiter, err = c.GetWaiterWithOptions(StatusWatcherStrategy) require.NoError(t, err) resources, err := c.Build(strings.NewReader(jobManifest), false) @@ -2170,7 +2174,7 @@ status: phase: Running ` var err error - c.Waiter, err = c.GetWaiter(StatusWatcherStrategy) + c.Waiter, err = c.GetWaiterWithOptions(StatusWatcherStrategy) require.NoError(t, err) resources, err := c.Build(strings.NewReader(podManifest), false) @@ -2182,3 +2186,100 @@ status: require.Error(t, err) assert.Contains(t, err.Error(), "context canceled", "expected context canceled error, got: %v", err) } + +// testStatusReader is a custom status reader for testing that returns a configurable status. +type testStatusReader struct { + supportedGK schema.GroupKind + status status.Status +} + +func (r *testStatusReader) Supports(gk schema.GroupKind) bool { + return gk == r.supportedGK +} + +func (r *testStatusReader) ReadStatus(_ context.Context, _ engine.ClusterReader, id object.ObjMetadata) (*event.ResourceStatus, error) { + return &event.ResourceStatus{ + Identifier: id, + Status: r.status, + Message: "test status reader", + }, nil +} + +func (r *testStatusReader) ReadStatusForObject(_ context.Context, _ engine.ClusterReader, u *unstructured.Unstructured) (*event.ResourceStatus, error) { + id := object.ObjMetadata{ + Namespace: u.GetNamespace(), + Name: u.GetName(), + GroupKind: u.GroupVersionKind().GroupKind(), + } + return &event.ResourceStatus{ + Identifier: id, + Status: r.status, + Message: "test status reader", + }, nil +} + +func TestClientStatusReadersPassedToStatusWaiter(t *testing.T) { + // This test verifies that Client.StatusReaders is correctly passed through + // to the statusWaiter when using the StatusWatcherStrategy. + // We use a custom status reader that immediately returns CurrentStatus for pods, + // which allows a pod without Ready condition to pass the wait. + podManifest := ` +apiVersion: v1 +kind: Pod +metadata: + name: test-pod + namespace: default +` + + c := newTestClient(t) + statusReaders := []engine.StatusReader{ + &testStatusReader{ + supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), + status: status.CurrentStatus, + }, + } + + var err error + c.Waiter, err = c.GetWaiterWithOptions(StatusWatcherStrategy, WithKStatusReaders(statusReaders...)) + require.NoError(t, err) + + resources, err := c.Build(strings.NewReader(podManifest), false) + require.NoError(t, err) + + // The pod has no Ready condition, but our custom reader returns CurrentStatus, + // so the wait should succeed immediately without timeout. + err = c.Wait(resources, time.Second*3) + require.NoError(t, err) +} + +func TestClientStatusReadersWithWaitWithJobs(t *testing.T) { + // This test verifies that Client.StatusReaders is correctly passed through + // to the statusWaiter when using WaitWithJobs. + jobManifest := ` +apiVersion: batch/v1 +kind: Job +metadata: + name: test-job + namespace: default +` + + c := newTestClient(t) + statusReaders := []engine.StatusReader{ + &testStatusReader{ + supportedGK: schema.GroupKind{Group: "batch", Kind: "Job"}, + status: status.CurrentStatus, + }, + } + + var err error + c.Waiter, err = c.GetWaiterWithOptions(StatusWatcherStrategy, WithKStatusReaders(statusReaders...)) + require.NoError(t, err) + + resources, err := c.Build(strings.NewReader(jobManifest), false) + require.NoError(t, err) + + // The job has no Complete condition, but our custom reader returns CurrentStatus, + // so the wait should succeed immediately without timeout. + err = c.WaitWithJobs(resources, time.Second*3) + require.NoError(t, err) +} diff --git a/pkg/kube/fake/failing_kube_client.go b/pkg/kube/fake/failing_kube_client.go index 31d0082cc..0f7787f79 100644 --- a/pkg/kube/fake/failing_kube_client.go +++ b/pkg/kube/fake/failing_kube_client.go @@ -47,6 +47,8 @@ type FailingKubeClient struct { WaitForDeleteError error WatchUntilReadyError error WaitDuration time.Duration + // RecordedWaitOptions stores the WaitOptions passed to GetWaiter for testing + RecordedWaitOptions []kube.WaitOption } var _ kube.Interface = &FailingKubeClient{} @@ -153,7 +155,13 @@ func (f *FailingKubeClient) BuildTable(r io.Reader, _ bool) (kube.ResourceList, } func (f *FailingKubeClient) GetWaiter(ws kube.WaitStrategy) (kube.Waiter, error) { - waiter, _ := f.PrintingKubeClient.GetWaiter(ws) + return f.GetWaiterWithOptions(ws) +} + +func (f *FailingKubeClient) GetWaiterWithOptions(ws kube.WaitStrategy, opts ...kube.WaitOption) (kube.Waiter, error) { + // Record the WaitOptions for testing + f.RecordedWaitOptions = append(f.RecordedWaitOptions, opts...) + waiter, _ := f.PrintingKubeClient.GetWaiterWithOptions(ws, opts...) printingKubeWaiter, _ := waiter.(*PrintingKubeWaiter) return &FailingKubeWaiter{ PrintingKubeWaiter: printingKubeWaiter, diff --git a/pkg/kube/fake/printer.go b/pkg/kube/fake/printer.go index a7aad1dac..e3fa11576 100644 --- a/pkg/kube/fake/printer.go +++ b/pkg/kube/fake/printer.go @@ -148,7 +148,11 @@ func (p *PrintingKubeClient) DeleteWithPropagationPolicy(resources kube.Resource return &kube.Result{Deleted: resources}, nil } -func (p *PrintingKubeClient) GetWaiter(_ kube.WaitStrategy) (kube.Waiter, error) { +func (p *PrintingKubeClient) GetWaiter(ws kube.WaitStrategy) (kube.Waiter, error) { + return p.GetWaiterWithOptions(ws) +} + +func (p *PrintingKubeClient) GetWaiterWithOptions(_ kube.WaitStrategy, _ ...kube.WaitOption) (kube.Waiter, error) { return &PrintingKubeWaiter{Out: p.Out, LogOutput: p.LogOutput}, nil } diff --git a/pkg/kube/interface.go b/pkg/kube/interface.go index cc934ae1e..63c784751 100644 --- a/pkg/kube/interface.go +++ b/pkg/kube/interface.go @@ -56,7 +56,7 @@ type Interface interface { // IsReachable checks whether the client is able to connect to the cluster. IsReachable() error - // Get Waiter gets the Kube.Waiter + // GetWaiter gets the Kube.Waiter. GetWaiter(ws WaitStrategy) (Waiter, error) // GetPodList lists all pods that match the specified listOptions @@ -99,3 +99,14 @@ type Waiter interface { // error. WatchUntilReady(resources ResourceList, timeout time.Duration) error } + +// InterfaceWaitOptions defines an interface that extends Interface with +// methods that accept wait options. +// +// TODO Helm 5: Remove InterfaceWaitOptions and integrate its method(s) into the Interface. +type InterfaceWaitOptions interface { + // GetWaiter gets the Kube.Waiter with options. + GetWaiterWithOptions(ws WaitStrategy, opts ...WaitOption) (Waiter, error) +} + +var _ InterfaceWaitOptions = (*Client)(nil) diff --git a/pkg/kube/options.go b/pkg/kube/options.go new file mode 100644 index 000000000..49c6229ba --- /dev/null +++ b/pkg/kube/options.go @@ -0,0 +1,45 @@ +/* +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 kube + +import ( + "context" + + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" +) + +// WaitOption is a function that configures an option for waiting on resources. +type WaitOption func(*waitOptions) + +// WithWaitContext sets the context for waiting on resources. +func WithWaitContext(ctx context.Context) WaitOption { + return func(wo *waitOptions) { + wo.ctx = ctx + } +} + +// WithKStatusReaders sets the status readers to be used while waiting on resources. +func WithKStatusReaders(readers ...engine.StatusReader) WaitOption { + return func(wo *waitOptions) { + wo.statusReaders = readers + } +} + +type waitOptions struct { + ctx context.Context + statusReaders []engine.StatusReader +} diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index a518f0c04..5acc6f19d 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -45,6 +45,7 @@ type statusWaiter struct { client dynamic.Interface restMapper meta.RESTMapper ctx context.Context + readers []engine.StatusReader } // DefaultStatusWatcherTimeout is the timeout used by the status waiter when a @@ -71,15 +72,13 @@ func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.D sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) jobSR := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) podSR := helmStatusReaders.NewCustomPodStatusReader(w.restMapper) - // We don't want to wait on any other resources as watchUntilReady is only for Helm hooks + // We don't want to wait on any other resources as watchUntilReady is only for Helm hooks. + // If custom readers are defined they can be used as Helm hooks support any resource. + // We put them in front since the DelegatingStatusReader uses the first reader that matches. genericSR := statusreaders.NewGenericStatusReader(w.restMapper, alwaysReady) sr := &statusreaders.DelegatingStatusReader{ - StatusReaders: []engine.StatusReader{ - jobSR, - podSR, - genericSR, - }, + StatusReaders: append(w.readers, jobSR, podSR, genericSR), } sw.StatusReader = sr return w.wait(ctx, resourceList, sw) @@ -93,6 +92,7 @@ func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) er defer cancel() slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) + sw.StatusReader = statusreaders.NewStatusReader(w.restMapper, w.readers...) return w.wait(ctx, resourceList, sw) } @@ -105,7 +105,8 @@ func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Dura slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) newCustomJobStatusReader := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) - customSR := statusreaders.NewStatusReader(w.restMapper, newCustomJobStatusReader) + readers := append(w.readers, newCustomJobStatusReader) + customSR := statusreaders.NewStatusReader(w.restMapper, readers...) sw.StatusReader = customSR return w.wait(ctx, resourceList, sw) } diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index a8ff4e0e6..a4e7ff62c 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -14,15 +14,21 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v3/pkg/kube" +package kube // import "helm.sh/helm/v4/pkg/kube" import ( + "context" "errors" "fmt" "strings" + "sync/atomic" "testing" "time" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine" + "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" + "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/cli-utils/pkg/object" "github.com/fluxcd/cli-utils/pkg/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -933,3 +939,272 @@ func TestStatusWaitMixedResources(t *testing.T) { }) } } + +// mockStatusReader is a custom status reader for testing that tracks when it's used +// and returns a configurable status for resources it supports. +type mockStatusReader struct { + supportedGK schema.GroupKind + status status.Status + callCount atomic.Int32 +} + +func (m *mockStatusReader) Supports(gk schema.GroupKind) bool { + return gk == m.supportedGK +} + +func (m *mockStatusReader) ReadStatus(_ context.Context, _ engine.ClusterReader, id object.ObjMetadata) (*event.ResourceStatus, error) { + m.callCount.Add(1) + return &event.ResourceStatus{ + Identifier: id, + Status: m.status, + Message: "mock status reader", + }, nil +} + +func (m *mockStatusReader) ReadStatusForObject(_ context.Context, _ engine.ClusterReader, u *unstructured.Unstructured) (*event.ResourceStatus, error) { + m.callCount.Add(1) + id := object.ObjMetadata{ + Namespace: u.GetNamespace(), + Name: u.GetName(), + GroupKind: u.GroupVersionKind().GroupKind(), + } + return &event.ResourceStatus{ + Identifier: id, + Status: m.status, + Message: "mock status reader", + }, nil +} + +func TestStatusWaitWithCustomReaders(t *testing.T) { + t.Parallel() + tests := []struct { + name string + objManifests []string + customReader *mockStatusReader + expectErrs []error + }{ + { + name: "custom reader makes pod immediately current", + objManifests: []string{podNoStatusManifest}, + customReader: &mockStatusReader{ + supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), + status: status.CurrentStatus, + }, + expectErrs: nil, + }, + { + name: "custom reader returns in-progress status", + objManifests: []string{podCurrentManifest}, + customReader: &mockStatusReader{ + supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), + status: status.InProgressStatus, + }, + expectErrs: []error{errors.New("resource not ready, name: current-pod, kind: Pod, status: InProgress"), errors.New("context deadline exceeded")}, + }, + { + name: "custom reader for different resource type is not used", + objManifests: []string{podCurrentManifest}, + customReader: &mockStatusReader{ + supportedGK: batchv1.SchemeGroupVersion.WithKind("Job").GroupKind(), + status: status.InProgressStatus, + }, + expectErrs: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + batchv1.SchemeGroupVersion.WithKind("Job"), + ) + statusWaiter := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + readers: []engine.StatusReader{tt.customReader}, + } + objs := getRuntimeObjFromManifests(t, tt.objManifests) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + assert.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + err := statusWaiter.Wait(resourceList, time.Second*3) + if tt.expectErrs != nil { + assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) + return + } + assert.NoError(t, err) + }) + } +} + +func TestStatusWaitWithJobsAndCustomReaders(t *testing.T) { + t.Parallel() + tests := []struct { + name string + objManifests []string + customReader *mockStatusReader + expectErrs []error + }{ + { + name: "custom reader makes job immediately current", + objManifests: []string{jobNoStatusManifest}, + customReader: &mockStatusReader{ + supportedGK: batchv1.SchemeGroupVersion.WithKind("Job").GroupKind(), + status: status.CurrentStatus, + }, + expectErrs: nil, + }, + { + name: "custom reader for pod works with WaitWithJobs", + objManifests: []string{podNoStatusManifest}, + customReader: &mockStatusReader{ + supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), + status: status.CurrentStatus, + }, + expectErrs: nil, + }, + { + name: "built-in job reader is still appended after custom readers", + objManifests: []string{jobCompleteManifest}, + customReader: &mockStatusReader{ + supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), + status: status.CurrentStatus, + }, + expectErrs: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + batchv1.SchemeGroupVersion.WithKind("Job"), + ) + statusWaiter := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + readers: []engine.StatusReader{tt.customReader}, + } + objs := getRuntimeObjFromManifests(t, tt.objManifests) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + assert.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + err := statusWaiter.WaitWithJobs(resourceList, time.Second*3) + if tt.expectErrs != nil { + assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) + return + } + assert.NoError(t, err) + }) + } +} + +func TestWatchUntilReadyWithCustomReaders(t *testing.T) { + t.Parallel() + tests := []struct { + name string + objManifests []string + customReader *mockStatusReader + expectErrs []error + }{ + { + name: "custom reader makes job immediately current for hooks", + objManifests: []string{jobNoStatusManifest}, + customReader: &mockStatusReader{ + supportedGK: batchv1.SchemeGroupVersion.WithKind("Job").GroupKind(), + status: status.CurrentStatus, + }, + expectErrs: nil, + }, + { + name: "custom reader makes pod immediately current for hooks", + objManifests: []string{podCurrentManifest}, + customReader: &mockStatusReader{ + supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), + status: status.CurrentStatus, + }, + expectErrs: nil, + }, + { + name: "custom reader takes precedence over built-in pod reader", + objManifests: []string{podCompleteManifest}, + customReader: &mockStatusReader{ + supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), + status: status.InProgressStatus, + }, + expectErrs: []error{errors.New("resource not ready, name: good-pod, kind: Pod, status: InProgress"), errors.New("context deadline exceeded")}, + }, + { + name: "custom reader takes precedence over built-in job reader", + objManifests: []string{jobCompleteManifest}, + customReader: &mockStatusReader{ + supportedGK: batchv1.SchemeGroupVersion.WithKind("Job").GroupKind(), + status: status.InProgressStatus, + }, + expectErrs: []error{errors.New("resource not ready, name: test, kind: Job, status: InProgress"), errors.New("context deadline exceeded")}, + }, + { + name: "custom reader for different resource type does not affect pods", + objManifests: []string{podCompleteManifest}, + customReader: &mockStatusReader{ + supportedGK: batchv1.SchemeGroupVersion.WithKind("Job").GroupKind(), + status: status.InProgressStatus, + }, + expectErrs: nil, + }, + { + name: "built-in readers still work when custom reader does not match", + objManifests: []string{jobCompleteManifest}, + customReader: &mockStatusReader{ + supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), + status: status.InProgressStatus, + }, + expectErrs: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + batchv1.SchemeGroupVersion.WithKind("Job"), + ) + statusWaiter := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + readers: []engine.StatusReader{tt.customReader}, + } + objs := getRuntimeObjFromManifests(t, tt.objManifests) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + assert.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + err := statusWaiter.WatchUntilReady(resourceList, time.Second*3) + if tt.expectErrs != nil { + assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) + return + } + assert.NoError(t, err) + }) + } +} From f46f1ce55d690a6c6f7e9b602757f9cf844492cb Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Mon, 12 Jan 2026 18:32:31 +0000 Subject: [PATCH 136/571] Update pkg/kube/statuswait.go Signed-off-by: Evans Mungai --- pkg/kube/statuswait.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index 5acc6f19d..75c18de81 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -105,7 +105,8 @@ func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Dura slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) newCustomJobStatusReader := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) - readers := append(w.readers, newCustomJobStatusReader) + readers := append([]engine.StatusReader(nil), w.readers...) + readers = append(readers, newCustomJobStatusReader) customSR := statusreaders.NewStatusReader(w.restMapper, readers...) sw.StatusReader = customSR return w.wait(ctx, resourceList, sw) From da1d68adea91ab13b308c059c39381d48045a73a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 02:06:52 +0000 Subject: [PATCH 137/571] chore(deps): bump golang.org/x/crypto from 0.46.0 to 0.47.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.46.0 to 0.47.0. - [Commits](https://github.com/golang/crypto/compare/v0.46.0...v0.47.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.47.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 81162118a..6e2d9c15d 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/tetratelabs/wazero v1.11.0 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.46.0 + golang.org/x/crypto v0.47.0 golang.org/x/term v0.39.0 golang.org/x/text v0.33.0 gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index abeb76478..b1e843f1b 100644 --- a/go.sum +++ b/go.sum @@ -376,8 +376,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= From a8eb5278478c940c615741312ca9f4fec0d84c1a Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Tue, 13 Jan 2026 15:37:24 +0000 Subject: [PATCH 138/571] bugfix(storage): fix storage not getting logger from driver Signed-off-by: Matheus Pimenta --- pkg/storage/storage.go | 6 ++++-- pkg/storage/storage_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index d6c41635b..d5d2ea317 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -339,12 +339,14 @@ func Init(d driver.Driver) *Storage { Driver: d, } + var h slog.Handler // Get logger from driver if it implements the LoggerSetterGetter interface if ls, ok := d.(logging.LoggerSetterGetter); ok { - ls.SetLogger(s.Logger().Handler()) + h = ls.Logger().Handler() } else { // If the driver does not implement the LoggerSetterGetter interface, set the default logger - s.SetLogger(slog.Default().Handler()) + h = slog.Default().Handler() } + s.SetLogger(h) return s } diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index 5b2a3bba5..0055c095c 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -17,8 +17,10 @@ limitations under the License. package storage // import "helm.sh/helm/v4/pkg/storage" import ( + "context" "errors" "fmt" + "log/slog" "reflect" "testing" @@ -579,3 +581,35 @@ func assertErrNil(eh func(args ...interface{}), err error, message string) { eh(fmt.Sprintf("%s: %q", message, err)) } } + +func TestStorageGetsLoggerFromDriver(t *testing.T) { + d := driver.NewMemory() + l := &mockSLogHandler{} + d.SetLogger(l) + s := Init(d) + _, _ = s.Get("doesnt-matter", 123) + if !l.Called { + t.Fatalf("Expected storage to use driver's logger, but it did not") + } +} + +type mockSLogHandler struct { + Called bool +} + +func (m *mockSLogHandler) Enabled(context.Context, slog.Level) bool { + return true +} + +func (m *mockSLogHandler) Handle(context.Context, slog.Record) error { + m.Called = true + return nil +} + +func (m *mockSLogHandler) WithAttrs([]slog.Attr) slog.Handler { + return m +} + +func (m *mockSLogHandler) WithGroup(string) slog.Handler { + return m +} From 6501ef490a45e9b7edfed1432702532c5b11c6d2 Mon Sep 17 00:00:00 2001 From: Manuel Alonso Date: Tue, 13 Jan 2026 19:21:16 +0100 Subject: [PATCH 139/571] chore(refactor): better testing and functionality for installing crd Signed-off-by: Manuel Alonso --- pkg/action/install.go | 48 ++++++++++++++++++-------------------- pkg/action/install_test.go | 40 +++++++++++-------------------- 2 files changed, 37 insertions(+), 51 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index b84e9eaf9..f1677c297 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -222,34 +222,32 @@ func (i *Install) installCRDs(crds []chart.CRD) error { return err } - if i.cfg.RESTClientGetter != nil { - // If we have already gathered the capabilities, we need to invalidate - // the cache so that the new CRDs are recognized. This should only be - // the case when an action configuration is reused for multiple actions, - // as otherwise it is later loaded by ourselves when getCapabilities - // is called later on in the installation process. - if i.cfg.Capabilities != nil { - discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() - if err != nil { - return err - } - - i.cfg.Logger().Debug("clearing discovery cache") - discoveryClient.Invalidate() - - _, _ = discoveryClient.ServerGroups() - } - - // Invalidate the REST mapper, since it will not have the new CRDs - // present. - restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper() + // If we have already gathered the capabilities, we need to invalidate + // the cache so that the new CRDs are recognized. This should only be + // the case when an action configuration is reused for multiple actions, + // as otherwise it is later loaded by ourselves when getCapabilities + // is called later on in the installation process. + if i.cfg.Capabilities != nil { + discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() if err != nil { return err } - if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { - i.cfg.Logger().Debug("clearing REST mapper cache") - resettable.Reset() - } + + i.cfg.Logger().Debug("clearing discovery cache") + discoveryClient.Invalidate() + + _, _ = discoveryClient.ServerGroups() + } + + // Invalidate the REST mapper, since it will not have the new CRDs + // present. + restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper() + if err != nil { + return err + } + if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { + i.cfg.Logger().Debug("clearing REST mapper cache") + resettable.Reset() } } return nil diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 76c35b628..003a29528 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -35,7 +35,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kuberuntime "k8s.io/apimachinery/pkg/runtime" @@ -45,6 +44,7 @@ import ( "k8s.io/client-go/rest/fake" ci "helm.sh/helm/v4/pkg/chart" + "helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/pkg/chart/common" @@ -1086,6 +1086,8 @@ func TestInstallSetRegistryClient(t *testing.T) { func TestInstallCRDs(t *testing.T) { config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false)) + config.RESTClientGetter = cli.New().RESTClientGetter() + instAction := NewInstall(config) mockFile := common.File{ @@ -1094,10 +1096,18 @@ func TestInstallCRDs(t *testing.T) { } mockChart := buildChart(withFile(mockFile)) crdsToInstall := mockChart.CRDObjects() - assert.Len(t, crdsToInstall, 1) - assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) - require.NoError(t, instAction.installCRDs(crdsToInstall)) + t.Run("fresh installation", func(t *testing.T) { + assert.Len(t, crdsToInstall, 1) + assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) + require.NoError(t, instAction.installCRDs(crdsToInstall)) + }) + + t.Run("already exist", func(t *testing.T) { + assert.Len(t, crdsToInstall, 1) + assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) + require.NoError(t, instAction.installCRDs(crdsToInstall)) + }) } func TestInstallCRDs_KubeClient_BuildError(t *testing.T) { @@ -1134,28 +1144,6 @@ func TestInstallCRDs_KubeClient_CreateError(t *testing.T) { require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") } -func TestInstallCRDs_AlreadyExist(t *testing.T) { - config := actionConfigFixture(t) - failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} - mockError := &apierrors.StatusError{ErrStatus: metav1.Status{ - Status: metav1.StatusFailure, - Reason: metav1.StatusReasonAlreadyExists, - }} - failingKubeClient.CreateError = mockError - config.KubeClient = &failingKubeClient - instAction := NewInstall(config) - - mockFile := common.File{ - Name: "crds/foo.yaml", - Data: []byte("hello"), - } - - mockChart := buildChart(withFile(mockFile)) - crdsToInstall := mockChart.CRDObjects() - - require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") -} - func TestInstallCRDs_WaiterError(t *testing.T) { config := actionConfigFixture(t) failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} From e22377124dbca4b032c55f522358def0415a0e8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 21:33:35 +0000 Subject: [PATCH 140/571] chore(deps): bump actions/setup-go from 6.1.0 to 6.2.0 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.1.0 to 6.2.0. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/4dc6199c7b1a012772edbd06daecab0f50c9053c...7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/release.yml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 54fa3a078..77e5d9343 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -22,7 +22,7 @@ jobs: - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # pin@6.1.0 + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # pin@6.2.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 9d5723329..ede3b4c71 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -17,7 +17,7 @@ jobs: - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # pin@6.1.0 + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # pin@6.2.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index e8f2560e3..b1a50b553 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -25,7 +25,7 @@ jobs: - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # pin@6.1.0 + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # pin@6.2.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf8595742..46c999191 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # pin@6.1.0 + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # pin@6.2.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true @@ -91,7 +91,7 @@ jobs: run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # pin@6.1.0 + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # pin@6.2.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true From 828038a8fe2142599ec557da2d12bb88b76fa0dd Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Wed, 14 Jan 2026 12:11:59 +0000 Subject: [PATCH 141/571] chore: clarify --wait flag help text Improve the description to explain the three usage scenarios: - --wait alone defaults to 'watcher' strategy - --wait= uses the specified strategy - flag omitted defaults to 'hookOnly' Signed-off-by: Evans Mungai --- pkg/cmd/flags.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index 251bfa032..6d9d117f8 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -59,9 +59,8 @@ func AddWaitFlag(cmd *cobra.Command, wait *kube.WaitStrategy) { cmd.Flags().Var( newWaitValue(kube.HookOnlyStrategy, wait), "wait", - "if specified, wait until resources are ready (up to --timeout). Values: 'watcher', 'hookOnly', and 'legacy'.", + "wait until resources are ready (up to --timeout). Use '--wait' alone for 'watcher' strategy, or specify one of: 'watcher', 'hookOnly', 'legacy'. Default when flag is omitted: 'hookOnly'.", ) - // Sets the strategy to use the watcher strategy if `--wait` is used without an argument cmd.Flags().Lookup("wait").NoOptDefVal = string(kube.StatusWatcherStrategy) } From c1cc6253232d697ad2ae29957cc49de223306b62 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Thu, 15 Jan 2026 10:04:07 +0000 Subject: [PATCH 142/571] chore(defaults): server-side apply SDK defaults should always match the CLI defaults Signed-off-by: Matheus Pimenta --- pkg/action/install.go | 2 +- pkg/action/rollback.go | 5 +++-- pkg/action/upgrade.go | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index 38355491a..0fe1f1a6e 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -159,7 +159,7 @@ type ChartPathOptions struct { func NewInstall(cfg *Configuration) *Install { in := &Install{ cfg: cfg, - ServerSideApply: true, + ServerSideApply: true, // Must always match the CLI default. DryRunStrategy: DryRunNone, } in.registryClient = cfg.RegistryClient diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 03150532e..459569781 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -64,8 +64,9 @@ type Rollback struct { // NewRollback creates a new Rollback object with the given configuration. func NewRollback(cfg *Configuration) *Rollback { return &Rollback{ - cfg: cfg, - DryRunStrategy: DryRunNone, + cfg: cfg, + ServerSideApply: "auto", // Must always match the CLI default. + DryRunStrategy: DryRunNone, } } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 4b99be603..4c93855b1 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -142,7 +142,7 @@ type resultMessage struct { func NewUpgrade(cfg *Configuration) *Upgrade { up := &Upgrade{ cfg: cfg, - ServerSideApply: "auto", + ServerSideApply: "auto", // Must always match the CLI default. DryRunStrategy: DryRunNone, } up.registryClient = cfg.RegistryClient From 443a2a6924fc384e87ff4251e5ac9c077d607f0f Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Fri, 16 Jan 2026 18:51:26 -0800 Subject: [PATCH 143/571] Modernize Helm v3 CONTRIBUTING.md Signed-off-by: George Jenkins --- CONTRIBUTING.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e809e7ca2..83e5a928e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,9 +11,13 @@ vulnerability_, please email a report to [cncf-helm-security@lists.cncf.io](mailto:cncf-helm-security@lists.cncf.io). This will give us a chance to try to fix the issue before it is exploited in the wild. -## Helm v3 and v4 +## Helm v3 -Helm v4 is currently under development on the `main` branch. During the development of Helm v4 and for some time after its released, Helm v3 will continue to be supported and developed on the `dev-v3` branch. Helm v3 will continue to get bug fixes and updates for new Kubernetes releases. Helm v4 is where new features and major changes will happen. For features to be backported to Helm v3, an exception will be needed. Bugs should first be fixed on Helm v4 and then backported to Helm v3. +Helm v4 is developed on the `main` branch. Helm v3 on the `dev-v3` branch. + +Helm v3 will continue to get bug fixes and updates for new Kubernetes releases until July 8th 2026. And security fixes until November 11th 2026. See the blog for more details. + +Bugs should first be fixed on Helm v4 and then backported to Helm v3. Helm v3 (and the `dev-v3` branch) is no longer accepting features. ## Sign Your Work From bbec77c1f762c4d92678e7f455951757a2e036a3 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Wed, 14 Jan 2026 22:42:12 +0000 Subject: [PATCH 144/571] bugfix(kstatus): do not wait forever on failed resources Signed-off-by: Matheus Pimenta --- pkg/kube/client_test.go | 61 ++++++-- pkg/kube/statuswait.go | 53 ++++--- pkg/kube/statuswait_test.go | 282 ++++++++++++++++++++++++++---------- 3 files changed, 288 insertions(+), 108 deletions(-) diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index e257c66d2..7e4cf046c 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -32,9 +32,11 @@ import ( "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event" "github.com/fluxcd/cli-utils/pkg/kstatus/status" "github.com/fluxcd/cli-utils/pkg/object" + "github.com/fluxcd/cli-utils/pkg/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -44,8 +46,10 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/resource" + dynamicfake "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/kubernetes" k8sfake "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/scheme" @@ -1241,7 +1245,7 @@ func (c createPatchTestCase) run(t *testing.T) { } } -func newTestCustomResourceData(metadata map[string]string, spec map[string]interface{}) *unstructured.Unstructured { +func newTestCustomResourceData(metadata map[string]string, spec map[string]any) *unstructured.Unstructured { if metadata == nil { metadata = make(map[string]string) } @@ -1251,7 +1255,7 @@ func newTestCustomResourceData(metadata map[string]string, spec map[string]inter if _, ok := metadata["namespace"]; !ok { metadata["namespace"] = "default" } - o := map[string]interface{}{ + o := map[string]any{ "apiVersion": "crd.com/v1", "kind": "Data", "metadata": metadata, @@ -1274,7 +1278,7 @@ func TestCreatePatchCustomResourceMetadata(t *testing.T) { name: "take ownership of resource", target: target, original: target, - actual: newTestCustomResourceData(nil, map[string]interface{}{ + actual: newTestCustomResourceData(nil, map[string]any{ "color": "red", }), threeWayMergeForUnstructured: true, @@ -1290,7 +1294,7 @@ func TestCreatePatchCustomResourceMetadata(t *testing.T) { } func TestCreatePatchCustomResourceSpec(t *testing.T) { - target := newTestCustomResourceData(nil, map[string]interface{}{ + target := newTestCustomResourceData(nil, map[string]any{ "color": "red", "size": "large", }) @@ -1298,7 +1302,7 @@ func TestCreatePatchCustomResourceSpec(t *testing.T) { name: "merge with spec of existing custom resource", target: target, original: target, - actual: newTestCustomResourceData(nil, map[string]interface{}{ + actual: newTestCustomResourceData(nil, map[string]any{ "color": "red", "weight": "heavy", }), @@ -2275,9 +2279,19 @@ metadata: }, } - var err error - c.Waiter, err = c.GetWaiterWithOptions(StatusWatcherStrategy, WithKStatusReaders(statusReaders...)) - require.NoError(t, err) + // Create a fake dynamic client with the pod resource + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper(v1.SchemeGroupVersion.WithKind("Pod")) + + // Create the pod in the fake client + createManifest(t, podManifest, fakeMapper, fakeClient) + + // Set up the waiter with the fake client and custom status readers + c.Waiter = &statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + readers: statusReaders, + } resources, err := c.Build(strings.NewReader(podManifest), false) require.NoError(t, err) @@ -2307,9 +2321,19 @@ metadata: }, } - var err error - c.Waiter, err = c.GetWaiterWithOptions(StatusWatcherStrategy, WithKStatusReaders(statusReaders...)) - require.NoError(t, err) + // Create a fake dynamic client with the job resource + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper(batchv1.SchemeGroupVersion.WithKind("Job")) + + // Create the job in the fake client + createManifest(t, jobManifest, fakeMapper, fakeClient) + + // Set up the waiter with the fake client and custom status readers + c.Waiter = &statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + readers: statusReaders, + } resources, err := c.Build(strings.NewReader(jobManifest), false) require.NoError(t, err) @@ -2319,3 +2343,18 @@ metadata: err = c.WaitWithJobs(resources, time.Second*3) require.NoError(t, err) } + +func createManifest(t *testing.T, manifest string, + fakeMapper meta.RESTMapper, fakeClient *dynamicfake.FakeDynamicClient) { + t.Helper() + + m := make(map[string]any) + err := yaml.Unmarshal([]byte(manifest), &m) + require.NoError(t, err) + obj := &unstructured.Unstructured{Object: m} + gvk := obj.GroupVersionKind() + mapping, err := fakeMapper.RESTMapping(gvk.GroupKind(), gvk.Version) + require.NoError(t, err) + err = fakeClient.Tracker().Create(mapping.Resource, obj, obj.GetNamespace()) + require.NoError(t, err) +} diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index 75c18de81..bd6e4f93a 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -145,17 +145,19 @@ func (w *statusWaiter) waitForDelete(ctx context.Context, resourceList ResourceL return statusCollector.Error } - // Only check parent context error, otherwise we would error when desired status is achieved. - if ctx.Err() != nil { - errs := []error{} - for _, id := range resources { - rs := statusCollector.ResourceStatuses[id] - if rs.Status == status.NotFoundStatus { - continue - } - errs = append(errs, fmt.Errorf("resource still exists, name: %s, kind: %s, status: %s", rs.Identifier.Name, rs.Identifier.GroupKind.Kind, rs.Status)) + errs := []error{} + for _, id := range resources { + rs := statusCollector.ResourceStatuses[id] + if rs.Status == status.NotFoundStatus || rs.Status == status.UnknownStatus { + continue } - errs = append(errs, ctx.Err()) + errs = append(errs, fmt.Errorf("resource %s/%s/%s still exists. status: %s, message: %s", + rs.Identifier.GroupKind.Kind, rs.Identifier.Namespace, rs.Identifier.Name, rs.Status, rs.Message)) + } + if err := ctx.Err(); err != nil { + errs = append(errs, err) + } + if len(errs) > 0 { return errors.Join(errs...) } return nil @@ -190,17 +192,19 @@ func (w *statusWaiter) wait(ctx context.Context, resourceList ResourceList, sw w return statusCollector.Error } - // Only check parent context error, otherwise we would error when desired status is achieved. - if ctx.Err() != nil { - errs := []error{} - for _, id := range resources { - rs := statusCollector.ResourceStatuses[id] - if rs.Status == status.CurrentStatus { - continue - } - errs = append(errs, fmt.Errorf("resource not ready, name: %s, kind: %s, status: %s", rs.Identifier.Name, rs.Identifier.GroupKind.Kind, rs.Status)) + errs := []error{} + for _, id := range resources { + rs := statusCollector.ResourceStatuses[id] + if rs.Status == status.CurrentStatus { + continue } - errs = append(errs, ctx.Err()) + errs = append(errs, fmt.Errorf("resource %s/%s/%s not ready. status: %s, message: %s", + rs.Identifier.GroupKind.Kind, rs.Identifier.Namespace, rs.Identifier.Name, rs.Status, rs.Message)) + } + if err := ctx.Err(); err != nil { + errs = append(errs, err) + } + if len(errs) > 0 { return errors.Join(errs...) } return nil @@ -225,11 +229,16 @@ func statusObserver(cancel context.CancelFunc, desired status.Status) collector. if rs == nil { continue } - // If a resource is already deleted before waiting has started, it will show as unknown - // this check ensures we don't wait forever for a resource that is already deleted + // If a resource is already deleted before waiting has started, it will show as unknown. + // This check ensures we don't wait forever for a resource that is already deleted. if rs.Status == status.UnknownStatus && desired == status.NotFoundStatus { continue } + // Failed is a terminal state. This check ensures we don't wait forever for a resource + // that has already failed, as intervention is required to resolve the failure. + if rs.Status == status.FailedStatus && desired == status.CurrentStatus { + continue + } rss = append(rss, rs) if rs.Status != desired { nonDesiredResources = append(nonDesiredResources, rs) diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index a4e7ff62c..0d60a526f 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -101,10 +101,27 @@ status: succeeded: 1 active: 0 conditions: - - type: Complete + - type: Complete status: "True" ` +var jobFailedManifest = ` +apiVersion: batch/v1 +kind: Job +metadata: + name: failed-job + namespace: default + generation: 1 +status: + failed: 1 + active: 0 + conditions: + - type: Failed + status: "True" + reason: BackoffLimitExceeded + message: "Job has reached the specified backoff limit" +` + var podCompleteManifest = ` apiVersion: v1 kind: Pod @@ -279,7 +296,7 @@ func TestStatusWaitForDelete(t *testing.T) { name string manifestsToCreate []string manifestsToDelete []string - expectErrs []error + expectErrs []string }{ { name: "wait for pod to be deleted", @@ -291,7 +308,7 @@ func TestStatusWaitForDelete(t *testing.T) { name: "error when not all objects are deleted", manifestsToCreate: []string{jobCompleteManifest, podCurrentManifest}, manifestsToDelete: []string{jobCompleteManifest}, - expectErrs: []error{errors.New("resource still exists, name: current-pod, kind: Pod, status: Current"), errors.New("context deadline exceeded")}, + expectErrs: []string{"resource Pod/ns/current-pod still exists. status: Current", "context deadline exceeded"}, }, } for _, tt := range tests { @@ -329,7 +346,10 @@ func TestStatusWaitForDelete(t *testing.T) { resourceList := getResourceListFromRuntimeObjs(t, c, objsToCreate) err := statusWaiter.WaitForDelete(resourceList, timeout) if tt.expectErrs != nil { - assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) + require.Error(t, err) + for _, expectedErrStr := range tt.expectErrs { + assert.Contains(t, err.Error(), expectedErrStr) + } return } assert.NoError(t, err) @@ -359,37 +379,35 @@ func TestStatusWaitForDeleteNonExistentObject(t *testing.T) { func TestStatusWait(t *testing.T) { t.Parallel() tests := []struct { - name string - objManifests []string - expectErrs []error - waitForJobs bool + name string + objManifests []string + expectErrStrs []string + waitForJobs bool }{ { - name: "Job is not complete", - objManifests: []string{jobNoStatusManifest}, - expectErrs: []error{errors.New("resource not ready, name: test, kind: Job, status: InProgress"), errors.New("context deadline exceeded")}, - waitForJobs: true, + name: "Job is not complete", + objManifests: []string{jobNoStatusManifest}, + expectErrStrs: []string{"resource Job/qual/test not ready. status: InProgress", "context deadline exceeded"}, + waitForJobs: true, }, { - name: "Job is ready but not complete", - objManifests: []string{jobReadyManifest}, - expectErrs: nil, - waitForJobs: false, + name: "Job is ready but not complete", + objManifests: []string{jobReadyManifest}, + expectErrStrs: nil, + waitForJobs: false, }, { name: "Pod is ready", objManifests: []string{podCurrentManifest}, - expectErrs: nil, }, { - name: "one of the pods never becomes ready", - objManifests: []string{podNoStatusManifest, podCurrentManifest}, - expectErrs: []error{errors.New("resource not ready, name: in-progress-pod, kind: Pod, status: InProgress"), errors.New("context deadline exceeded")}, + name: "one of the pods never becomes ready", + objManifests: []string{podNoStatusManifest, podCurrentManifest}, + expectErrStrs: []string{"resource Pod/ns/in-progress-pod not ready. status: InProgress", "context deadline exceeded"}, }, { name: "paused deployment passes", objManifests: []string{pausedDeploymentManifest}, - expectErrs: nil, }, } @@ -416,8 +434,11 @@ func TestStatusWait(t *testing.T) { } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.Wait(resourceList, time.Second*3) - if tt.expectErrs != nil { - assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) + if tt.expectErrStrs != nil { + require.Error(t, err) + for _, expectedErrStr := range tt.expectErrStrs { + assert.Contains(t, err.Error(), expectedErrStr) + } return } assert.NoError(t, err) @@ -428,23 +449,23 @@ func TestStatusWait(t *testing.T) { func TestWaitForJobComplete(t *testing.T) { t.Parallel() tests := []struct { - name string - objManifests []string - expectErrs []error + name string + objManifests []string + expectErrStrs []string }{ { name: "Job is complete", objManifests: []string{jobCompleteManifest}, }, { - name: "Job is not ready", - objManifests: []string{jobNoStatusManifest}, - expectErrs: []error{errors.New("resource not ready, name: test, kind: Job, status: InProgress"), errors.New("context deadline exceeded")}, + name: "Job is not ready", + objManifests: []string{jobNoStatusManifest}, + expectErrStrs: []string{"resource Job/qual/test not ready. status: InProgress", "context deadline exceeded"}, }, { - name: "Job is ready but not complete", - objManifests: []string{jobReadyManifest}, - expectErrs: []error{errors.New("resource not ready, name: ready-not-complete, kind: Job, status: InProgress"), errors.New("context deadline exceeded")}, + name: "Job is ready but not complete", + objManifests: []string{jobReadyManifest}, + expectErrStrs: []string{"resource Job/default/ready-not-complete not ready. status: InProgress", "context deadline exceeded"}, }, } @@ -469,8 +490,11 @@ func TestWaitForJobComplete(t *testing.T) { } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.WaitWithJobs(resourceList, time.Second*3) - if tt.expectErrs != nil { - assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) + if tt.expectErrStrs != nil { + require.Error(t, err) + for _, expectedErrStr := range tt.expectErrStrs { + assert.Contains(t, err.Error(), expectedErrStr) + } return } assert.NoError(t, err) @@ -481,9 +505,9 @@ func TestWaitForJobComplete(t *testing.T) { func TestWatchForReady(t *testing.T) { t.Parallel() tests := []struct { - name string - objManifests []string - expectErrs []error + name string + objManifests []string + expectErrStrs []string }{ { name: "succeeds if pod and job are complete", @@ -494,14 +518,14 @@ func TestWatchForReady(t *testing.T) { objManifests: []string{notReadyDeploymentManifest}, }, { - name: "Fails if job is not complete", - objManifests: []string{jobReadyManifest}, - expectErrs: []error{errors.New("resource not ready, name: ready-not-complete, kind: Job, status: InProgress"), errors.New("context deadline exceeded")}, + name: "Fails if job is not complete", + objManifests: []string{jobReadyManifest}, + expectErrStrs: []string{"resource Job/default/ready-not-complete not ready. status: InProgress", "context deadline exceeded"}, }, { - name: "Fails if pod is not complete", - objManifests: []string{podCurrentManifest}, - expectErrs: []error{errors.New("resource not ready, name: current-pod, kind: Pod, status: InProgress"), errors.New("context deadline exceeded")}, + name: "Fails if pod is not complete", + objManifests: []string{podCurrentManifest}, + expectErrStrs: []string{"resource Pod/ns/current-pod not ready. status: InProgress", "context deadline exceeded"}, }, } @@ -528,8 +552,11 @@ func TestWatchForReady(t *testing.T) { } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.WatchUntilReady(resourceList, time.Second*3) - if tt.expectErrs != nil { - assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) + if tt.expectErrStrs != nil { + require.Error(t, err) + for _, expectedErrStr := range tt.expectErrStrs { + assert.Contains(t, err.Error(), expectedErrStr) + } return } assert.NoError(t, err) @@ -540,10 +567,10 @@ func TestWatchForReady(t *testing.T) { func TestStatusWaitMultipleNamespaces(t *testing.T) { t.Parallel() tests := []struct { - name string - objManifests []string - expectErrs []error - testFunc func(statusWaiter, ResourceList, time.Duration) error + name string + objManifests []string + expectErrStrs []string + testFunc func(statusWaiter, ResourceList, time.Duration) error }{ { name: "pods in multiple namespaces", @@ -560,9 +587,9 @@ func TestStatusWaitMultipleNamespaces(t *testing.T) { }, }, { - name: "error when resource not ready in one namespace", - objManifests: []string{podNamespace1NoStatusManifest, podNamespace2Manifest}, - expectErrs: []error{errors.New("resource not ready, name: pod-ns1, kind: Pod, status: InProgress"), errors.New("context deadline exceeded")}, + name: "error when resource not ready in one namespace", + objManifests: []string{podNamespace1NoStatusManifest, podNamespace2Manifest}, + expectErrStrs: []string{"resource Pod/namespace-1/pod-ns1 not ready. status: InProgress", "context deadline exceeded"}, testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, @@ -642,8 +669,11 @@ func TestStatusWaitMultipleNamespaces(t *testing.T) { resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := tt.testFunc(sw, resourceList, time.Second*3) - if tt.expectErrs != nil { - assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) + if tt.expectErrStrs != nil { + require.Error(t, err) + for _, expectedErrStr := range tt.expectErrStrs { + assert.Contains(t, err.Error(), expectedErrStr) + } return } assert.NoError(t, err) @@ -978,10 +1008,10 @@ func (m *mockStatusReader) ReadStatusForObject(_ context.Context, _ engine.Clust func TestStatusWaitWithCustomReaders(t *testing.T) { t.Parallel() tests := []struct { - name string - objManifests []string - customReader *mockStatusReader - expectErrs []error + name string + objManifests []string + customReader *mockStatusReader + expectErrStrs []string }{ { name: "custom reader makes pod immediately current", @@ -990,7 +1020,6 @@ func TestStatusWaitWithCustomReaders(t *testing.T) { supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), status: status.CurrentStatus, }, - expectErrs: nil, }, { name: "custom reader returns in-progress status", @@ -999,7 +1028,7 @@ func TestStatusWaitWithCustomReaders(t *testing.T) { supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), status: status.InProgressStatus, }, - expectErrs: []error{errors.New("resource not ready, name: current-pod, kind: Pod, status: InProgress"), errors.New("context deadline exceeded")}, + expectErrStrs: []string{"resource Pod/ns/current-pod not ready. status: InProgress", "context deadline exceeded"}, }, { name: "custom reader for different resource type is not used", @@ -1008,7 +1037,6 @@ func TestStatusWaitWithCustomReaders(t *testing.T) { supportedGK: batchv1.SchemeGroupVersion.WithKind("Job").GroupKind(), status: status.InProgressStatus, }, - expectErrs: nil, }, } @@ -1035,8 +1063,11 @@ func TestStatusWaitWithCustomReaders(t *testing.T) { } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.Wait(resourceList, time.Second*3) - if tt.expectErrs != nil { - assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) + if tt.expectErrStrs != nil { + require.Error(t, err) + for _, expectedErrStr := range tt.expectErrStrs { + assert.Contains(t, err.Error(), expectedErrStr) + } return } assert.NoError(t, err) @@ -1113,13 +1144,115 @@ func TestStatusWaitWithJobsAndCustomReaders(t *testing.T) { } } +func TestStatusWaitWithFailedResources(t *testing.T) { + t.Parallel() + tests := []struct { + name string + objManifests []string + customReader *mockStatusReader + expectErrStrs []string + testFunc func(statusWaiter, ResourceList, time.Duration) error + }{ + { + name: "Wait returns error when resource has failed", + objManifests: []string{podNoStatusManifest}, + customReader: &mockStatusReader{ + supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), + status: status.FailedStatus, + }, + expectErrStrs: []string{"resource Pod/ns/in-progress-pod not ready. status: Failed, message: mock status reader"}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.Wait(rl, timeout) + }, + }, + { + name: "WaitWithJobs returns error when job has failed", + objManifests: []string{jobFailedManifest}, + customReader: nil, // Use the built-in job status reader + expectErrStrs: []string{ + "resource Job/default/failed-job not ready. status: Failed", + }, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.WaitWithJobs(rl, timeout) + }, + }, + { + name: "Wait returns errors when multiple resources fail", + objManifests: []string{podNoStatusManifest, podCurrentManifest}, + customReader: &mockStatusReader{ + supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), + status: status.FailedStatus, + }, + // The mock reader will make both pods return FailedStatus + expectErrStrs: []string{ + "resource Pod/ns/in-progress-pod not ready. status: Failed, message: mock status reader", + "resource Pod/ns/current-pod not ready. status: Failed, message: mock status reader", + }, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.Wait(rl, timeout) + }, + }, + { + name: "WatchUntilReady returns error when resource has failed", + objManifests: []string{podNoStatusManifest}, + customReader: &mockStatusReader{ + supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), + status: status.FailedStatus, + }, + // WatchUntilReady also waits for CurrentStatus, so failed resources should return error + expectErrStrs: []string{"resource Pod/ns/in-progress-pod not ready. status: Failed, message: mock status reader"}, + testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + return sw.WatchUntilReady(rl, timeout) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + batchv1.SchemeGroupVersion.WithKind("Job"), + ) + var readers []engine.StatusReader + if tt.customReader != nil { + readers = []engine.StatusReader{tt.customReader} + } + sw := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + readers: readers, + } + objs := getRuntimeObjFromManifests(t, tt.objManifests) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + assert.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + err := tt.testFunc(sw, resourceList, time.Second*3) + if tt.expectErrStrs != nil { + require.Error(t, err) + for _, expectedErrStr := range tt.expectErrStrs { + assert.Contains(t, err.Error(), expectedErrStr) + } + return + } + assert.NoError(t, err) + }) + } +} + func TestWatchUntilReadyWithCustomReaders(t *testing.T) { t.Parallel() tests := []struct { - name string - objManifests []string - customReader *mockStatusReader - expectErrs []error + name string + objManifests []string + customReader *mockStatusReader + expectErrStrs []string }{ { name: "custom reader makes job immediately current for hooks", @@ -1128,7 +1261,6 @@ func TestWatchUntilReadyWithCustomReaders(t *testing.T) { supportedGK: batchv1.SchemeGroupVersion.WithKind("Job").GroupKind(), status: status.CurrentStatus, }, - expectErrs: nil, }, { name: "custom reader makes pod immediately current for hooks", @@ -1137,7 +1269,6 @@ func TestWatchUntilReadyWithCustomReaders(t *testing.T) { supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), status: status.CurrentStatus, }, - expectErrs: nil, }, { name: "custom reader takes precedence over built-in pod reader", @@ -1146,7 +1277,7 @@ func TestWatchUntilReadyWithCustomReaders(t *testing.T) { supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), status: status.InProgressStatus, }, - expectErrs: []error{errors.New("resource not ready, name: good-pod, kind: Pod, status: InProgress"), errors.New("context deadline exceeded")}, + expectErrStrs: []string{"resource Pod/ns/good-pod not ready. status: InProgress", "context deadline exceeded"}, }, { name: "custom reader takes precedence over built-in job reader", @@ -1155,7 +1286,7 @@ func TestWatchUntilReadyWithCustomReaders(t *testing.T) { supportedGK: batchv1.SchemeGroupVersion.WithKind("Job").GroupKind(), status: status.InProgressStatus, }, - expectErrs: []error{errors.New("resource not ready, name: test, kind: Job, status: InProgress"), errors.New("context deadline exceeded")}, + expectErrStrs: []string{"resource Job/qual/test not ready. status: InProgress", "context deadline exceeded"}, }, { name: "custom reader for different resource type does not affect pods", @@ -1164,7 +1295,6 @@ func TestWatchUntilReadyWithCustomReaders(t *testing.T) { supportedGK: batchv1.SchemeGroupVersion.WithKind("Job").GroupKind(), status: status.InProgressStatus, }, - expectErrs: nil, }, { name: "built-in readers still work when custom reader does not match", @@ -1173,7 +1303,6 @@ func TestWatchUntilReadyWithCustomReaders(t *testing.T) { supportedGK: v1.SchemeGroupVersion.WithKind("Pod").GroupKind(), status: status.InProgressStatus, }, - expectErrs: nil, }, } @@ -1200,8 +1329,11 @@ func TestWatchUntilReadyWithCustomReaders(t *testing.T) { } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.WatchUntilReady(resourceList, time.Second*3) - if tt.expectErrs != nil { - assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) + if tt.expectErrStrs != nil { + require.Error(t, err) + for _, expectedErrStr := range tt.expectErrStrs { + assert.Contains(t, err.Error(), expectedErrStr) + } return } assert.NoError(t, err) From e70d59de7cd7ea2a501d809b3245ebfa0412e0ec Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Mon, 19 Jan 2026 16:42:04 +0000 Subject: [PATCH 145/571] docs: document uninstall using cascade foreground flag Signed-off-by: Evans Mungai --- pkg/cmd/uninstall.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/uninstall.go b/pkg/cmd/uninstall.go index 4cc14ae1e..49f7bd19d 100644 --- a/pkg/cmd/uninstall.go +++ b/pkg/cmd/uninstall.go @@ -35,6 +35,9 @@ as well as the release history, freeing it up for future use. Use the '--dry-run' flag to see which releases will be uninstalled without actually uninstalling them. + +Use '--cascade foreground' with '--wait' to ensure resources with finalizers +are fully deleted before the command returns. ` func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { @@ -76,7 +79,7 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation") f.BoolVar(&client.IgnoreNotFound, "ignore-not-found", false, `Treat "release not found" as a successful uninstall`) f.BoolVar(&client.KeepHistory, "keep-history", false, "remove all associated resources and mark the release as deleted, but retain the release history") - f.StringVar(&client.DeletionPropagation, "cascade", "background", "Must be \"background\", \"orphan\", or \"foreground\". Selects the deletion cascading strategy for the dependents. Defaults to background.") + f.StringVar(&client.DeletionPropagation, "cascade", "background", "Must be \"background\", \"orphan\", or \"foreground\". Selects the deletion cascading strategy for the dependents. Defaults to background. Use \"foreground\" with --wait to ensure resources with finalizers are fully deleted before returning.") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.StringVar(&client.Description, "description", "", "add a custom description") AddWaitFlag(cmd, &client.WaitStrategy) From 52620076e21ad6afd0f48df6772001b1466c966b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 22:34:25 +0000 Subject: [PATCH 146/571] chore(deps): bump sigs.k8s.io/controller-runtime from 0.22.4 to 0.23.0 Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.22.4 to 0.23.0. - [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.22.4...v0.23.0) --- updated-dependencies: - dependency-name: sigs.k8s.io/controller-runtime dependency-version: 0.23.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6e2d9c15d..4b0bb8977 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( k8s.io/klog/v2 v2.130.1 k8s.io/kubectl v0.35.0 oras.land/oras-go/v2 v2.6.0 - sigs.k8s.io/controller-runtime v0.22.4 + sigs.k8s.io/controller-runtime v0.23.0 sigs.k8s.io/kustomize/kyaml v0.21.0 sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index b1e843f1b..6e010e4b2 100644 --- a/go.sum +++ b/go.sum @@ -502,8 +502,8 @@ k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzk k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= -sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= -sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/controller-runtime v0.23.0 h1:Ubi7klJWiwEWqDY+odSVZiFA0aDSevOCXpa38yCSYu8= +sigs.k8s.io/controller-runtime v0.23.0/go.mod h1:DBOIr9NsprUqCZ1ZhsuJ0wAnQSIxY/C6VjZbmLgw0j0= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= From 97fd00786f16ebc3b68164bff7133592f19f70b6 Mon Sep 17 00:00:00 2001 From: Jeevan Yewale Date: Mon, 19 Jan 2026 12:40:51 +0530 Subject: [PATCH 147/571] Remove legacy sync-repo.sh script Signed-off-by: Jeevan Yewale --- scripts/sync-repo.sh | 83 -------------------------------------------- 1 file changed, 83 deletions(-) delete mode 100755 scripts/sync-repo.sh diff --git a/scripts/sync-repo.sh b/scripts/sync-repo.sh deleted file mode 100755 index 0651d3634..000000000 --- a/scripts/sync-repo.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env bash - -# 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. - -# Bash 'Strict Mode' -# http://redsymbol.net/articles/unofficial-bash-strict-mode -set -euo pipefail -IFS=$'\n\t' - -# Helper Functions ------------------------------------------------------------- - -# Display error message and exit -error_exit() { - echo "error: ${1:-"unknown error"}" 1>&2 - exit 1 -} - -# Checks if a command exists. Returns 1 or 0 -command_exists() { - hash "${1}" 2>/dev/null -} - -# Program Functions ------------------------------------------------------------ - -verify_prereqs() { - echo "Verifying Prerequisites...." - if command_exists gsutil; then - echo "Thumbs up! Looks like you have gsutil. Let's continue." - else - error_exit "Couldn't find gsutil. Bailing out." - fi -} - -confirm() { - # shellcheck disable=SC2154 - case $response in - [yY][eE][sS]|[yY]) - true - ;; - *) - false - ;; - esac -} - -# Main ------------------------------------------------------------------------- - -main() { - if [ "$#" -ne 2 ]; then - error_exit "Illegal number of parameters. You must pass in local directory path and a GCS bucket name" - fi - - echo "Getting ready to sync your local directory ($1) to a remote repository at gs://$2" - - verify_prereqs - - # dry run of the command - gsutil rsync -d -n "$1" gs://"$2" - - read -r -p "Are you sure you would like to continue with these changes? [y/N] " confirm - if [[ $confirm =~ [yY](es)* ]]; then - gsutil rsync -d "$1" gs://"$2" - else - error_exit "Discontinuing sync process." - fi - - echo "Your remote chart repository now matches the contents of the $1 directory!" - -} - -main "${@:-}" From 26e28e846af1ceaf63e16c4f2e52bbfbab411ba1 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Tue, 20 Jan 2026 16:24:21 -0800 Subject: [PATCH 148/571] Apply suggestions from code review Co-authored-by: Andrew Block Signed-off-by: George Jenkins --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 83e5a928e..0b704aa9a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,11 +13,11 @@ chance to try to fix the issue before it is exploited in the wild. ## Helm v3 -Helm v4 is developed on the `main` branch. Helm v3 on the `dev-v3` branch. +Helm v4 development takes place on the `main` branch while Helm v3 is on the `dev-v3` branch. -Helm v3 will continue to get bug fixes and updates for new Kubernetes releases until July 8th 2026. And security fixes until November 11th 2026. See the blog for more details. +Helm v3 will continue to receive bug fixes and updates for new Kubernetes releases until July 8th 2026. Security enhancement will still be applied until November 11th 2026. See the blog for more details. -Bugs should first be fixed on Helm v4 and then backported to Helm v3. Helm v3 (and the `dev-v3` branch) is no longer accepting features. +Bugs should first be fixed on Helm v4 and then backported to Helm v3. Helm v3 (and the `dev-v3` branch) is no longer accepting new features. ## Sign Your Work From b0b35f1231b0b885b1624c5586938cfa69d30995 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Thu, 15 Jan 2026 14:53:15 +0000 Subject: [PATCH 149/571] feat(kstatus): fine-grained context options for waiting Signed-off-by: Matheus Pimenta --- pkg/kube/client.go | 12 +- pkg/kube/options.go | 41 +++- pkg/kube/statuswait.go | 27 ++- pkg/kube/statuswait_test.go | 469 ++++++++++++++++++++++++++++++++++++ 4 files changed, 533 insertions(+), 16 deletions(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 9af4bbcb3..ad8f851d1 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -167,10 +167,14 @@ func (c *Client) newStatusWatcher(opts ...WaitOption) (*statusWaiter, error) { waitContext = c.WaitContext } return &statusWaiter{ - restMapper: restMapper, - client: dynamicClient, - ctx: waitContext, - readers: o.statusReaders, + restMapper: restMapper, + client: dynamicClient, + ctx: waitContext, + watchUntilReadyCtx: o.watchUntilReadyCtx, + waitCtx: o.waitCtx, + waitWithJobsCtx: o.waitWithJobsCtx, + waitForDeleteCtx: o.waitForDeleteCtx, + readers: o.statusReaders, }, nil } diff --git a/pkg/kube/options.go b/pkg/kube/options.go index 49c6229ba..3326c284b 100644 --- a/pkg/kube/options.go +++ b/pkg/kube/options.go @@ -26,12 +26,45 @@ import ( type WaitOption func(*waitOptions) // WithWaitContext sets the context for waiting on resources. +// If unset, context.Background() will be used. func WithWaitContext(ctx context.Context) WaitOption { return func(wo *waitOptions) { wo.ctx = ctx } } +// WithWatchUntilReadyMethodContext sets the context specifically for the WatchUntilReady method. +// If unset, the context set by `WithWaitContext` will be used (falling back to `context.Background()`). +func WithWatchUntilReadyMethodContext(ctx context.Context) WaitOption { + return func(wo *waitOptions) { + wo.watchUntilReadyCtx = ctx + } +} + +// WithWaitMethodContext sets the context specifically for the Wait method. +// If unset, the context set by `WithWaitContext` will be used (falling back to `context.Background()`). +func WithWaitMethodContext(ctx context.Context) WaitOption { + return func(wo *waitOptions) { + wo.waitCtx = ctx + } +} + +// WithWaitWithJobsMethodContext sets the context specifically for the WaitWithJobs method. +// If unset, the context set by `WithWaitContext` will be used (falling back to `context.Background()`). +func WithWaitWithJobsMethodContext(ctx context.Context) WaitOption { + return func(wo *waitOptions) { + wo.waitWithJobsCtx = ctx + } +} + +// WithWaitForDeleteMethodContext sets the context specifically for the WaitForDelete method. +// If unset, the context set by `WithWaitContext` will be used (falling back to `context.Background()`). +func WithWaitForDeleteMethodContext(ctx context.Context) WaitOption { + return func(wo *waitOptions) { + wo.waitForDeleteCtx = ctx + } +} + // WithKStatusReaders sets the status readers to be used while waiting on resources. func WithKStatusReaders(readers ...engine.StatusReader) WaitOption { return func(wo *waitOptions) { @@ -40,6 +73,10 @@ func WithKStatusReaders(readers ...engine.StatusReader) WaitOption { } type waitOptions struct { - ctx context.Context - statusReaders []engine.StatusReader + ctx context.Context + watchUntilReadyCtx context.Context + waitCtx context.Context + waitWithJobsCtx context.Context + waitForDeleteCtx context.Context + statusReaders []engine.StatusReader } diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index bd6e4f93a..84819492b 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -42,10 +42,14 @@ import ( ) type statusWaiter struct { - client dynamic.Interface - restMapper meta.RESTMapper - ctx context.Context - readers []engine.StatusReader + client dynamic.Interface + restMapper meta.RESTMapper + ctx context.Context + watchUntilReadyCtx context.Context + waitCtx context.Context + waitWithJobsCtx context.Context + waitForDeleteCtx context.Context + readers []engine.StatusReader } // DefaultStatusWatcherTimeout is the timeout used by the status waiter when a @@ -66,7 +70,7 @@ func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.D if timeout == 0 { timeout = DefaultStatusWatcherTimeout } - ctx, cancel := w.contextWithTimeout(timeout) + ctx, cancel := w.contextWithTimeout(w.watchUntilReadyCtx, timeout) defer cancel() slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) @@ -88,7 +92,7 @@ func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) er if timeout == 0 { timeout = DefaultStatusWatcherTimeout } - ctx, cancel := w.contextWithTimeout(timeout) + ctx, cancel := w.contextWithTimeout(w.waitCtx, timeout) defer cancel() slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) @@ -100,7 +104,7 @@ func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Dura if timeout == 0 { timeout = DefaultStatusWatcherTimeout } - ctx, cancel := w.contextWithTimeout(timeout) + ctx, cancel := w.contextWithTimeout(w.waitWithJobsCtx, timeout) defer cancel() slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) @@ -116,7 +120,7 @@ func (w *statusWaiter) WaitForDelete(resourceList ResourceList, timeout time.Dur if timeout == 0 { timeout = DefaultStatusWatcherTimeout } - ctx, cancel := w.contextWithTimeout(timeout) + ctx, cancel := w.contextWithTimeout(w.waitForDeleteCtx, timeout) defer cancel() slog.Debug("waiting for resources to be deleted", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) @@ -210,8 +214,11 @@ func (w *statusWaiter) wait(ctx context.Context, resourceList ResourceList, sw w return nil } -func (w *statusWaiter) contextWithTimeout(timeout time.Duration) (context.Context, context.CancelFunc) { - return contextWithTimeout(w.ctx, timeout) +func (w *statusWaiter) contextWithTimeout(methodCtx context.Context, timeout time.Duration) (context.Context, context.CancelFunc) { + if methodCtx == nil { + methodCtx = w.ctx + } + return contextWithTimeout(methodCtx, timeout) } func contextWithTimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc) { diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index 0d60a526f..83aaa357a 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -1246,6 +1246,475 @@ func TestStatusWaitWithFailedResources(t *testing.T) { } } +func TestWaitOptionFunctions(t *testing.T) { + t.Parallel() + + t.Run("WithWatchUntilReadyMethodContext sets watchUntilReadyCtx", func(t *testing.T) { + t.Parallel() + type contextKey struct{} + ctx := context.WithValue(context.Background(), contextKey{}, "test") + opts := &waitOptions{} + WithWatchUntilReadyMethodContext(ctx)(opts) + assert.Equal(t, ctx, opts.watchUntilReadyCtx) + }) + + t.Run("WithWaitMethodContext sets waitCtx", func(t *testing.T) { + t.Parallel() + type contextKey struct{} + ctx := context.WithValue(context.Background(), contextKey{}, "test") + opts := &waitOptions{} + WithWaitMethodContext(ctx)(opts) + assert.Equal(t, ctx, opts.waitCtx) + }) + + t.Run("WithWaitWithJobsMethodContext sets waitWithJobsCtx", func(t *testing.T) { + t.Parallel() + type contextKey struct{} + ctx := context.WithValue(context.Background(), contextKey{}, "test") + opts := &waitOptions{} + WithWaitWithJobsMethodContext(ctx)(opts) + assert.Equal(t, ctx, opts.waitWithJobsCtx) + }) + + t.Run("WithWaitForDeleteMethodContext sets waitForDeleteCtx", func(t *testing.T) { + t.Parallel() + type contextKey struct{} + ctx := context.WithValue(context.Background(), contextKey{}, "test") + opts := &waitOptions{} + WithWaitForDeleteMethodContext(ctx)(opts) + assert.Equal(t, ctx, opts.waitForDeleteCtx) + }) +} + +func TestMethodSpecificContextCancellation(t *testing.T) { + t.Parallel() + + t.Run("WatchUntilReady uses method-specific context", func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + ) + + // Create a cancelled method-specific context + methodCtx, methodCancel := context.WithCancel(context.Background()) + methodCancel() // Cancel immediately + + sw := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + ctx: context.Background(), // General context is not cancelled + watchUntilReadyCtx: methodCtx, // Method context is cancelled + } + + objs := getRuntimeObjFromManifests(t, []string{podCompleteManifest}) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + require.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + + err := sw.WatchUntilReady(resourceList, time.Second*3) + // Should fail due to cancelled method context + require.Error(t, err) + assert.Contains(t, err.Error(), "context canceled") + }) + + t.Run("Wait uses method-specific context", func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + ) + + // Create a cancelled method-specific context + methodCtx, methodCancel := context.WithCancel(context.Background()) + methodCancel() // Cancel immediately + + sw := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + ctx: context.Background(), // General context is not cancelled + waitCtx: methodCtx, // Method context is cancelled + } + + objs := getRuntimeObjFromManifests(t, []string{podCurrentManifest}) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + require.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + + err := sw.Wait(resourceList, time.Second*3) + // Should fail due to cancelled method context + require.Error(t, err) + assert.Contains(t, err.Error(), "context canceled") + }) + + t.Run("WaitWithJobs uses method-specific context", func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + batchv1.SchemeGroupVersion.WithKind("Job"), + ) + + // Create a cancelled method-specific context + methodCtx, methodCancel := context.WithCancel(context.Background()) + methodCancel() // Cancel immediately + + sw := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + ctx: context.Background(), // General context is not cancelled + waitWithJobsCtx: methodCtx, // Method context is cancelled + } + + objs := getRuntimeObjFromManifests(t, []string{jobCompleteManifest}) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + require.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + + err := sw.WaitWithJobs(resourceList, time.Second*3) + // Should fail due to cancelled method context + require.Error(t, err) + assert.Contains(t, err.Error(), "context canceled") + }) + + t.Run("WaitForDelete uses method-specific context", func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + ) + + // Create a cancelled method-specific context + methodCtx, methodCancel := context.WithCancel(context.Background()) + methodCancel() // Cancel immediately + + sw := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + ctx: context.Background(), // General context is not cancelled + waitForDeleteCtx: methodCtx, // Method context is cancelled + } + + objs := getRuntimeObjFromManifests(t, []string{podCurrentManifest}) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + require.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + + err := sw.WaitForDelete(resourceList, time.Second*3) + // Should fail due to cancelled method context + require.Error(t, err) + assert.Contains(t, err.Error(), "context canceled") + }) +} + +func TestMethodContextFallbackToGeneralContext(t *testing.T) { + t.Parallel() + + t.Run("WatchUntilReady falls back to general context when method context is nil", func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + ) + + // Create a cancelled general context + generalCtx, generalCancel := context.WithCancel(context.Background()) + generalCancel() // Cancel immediately + + sw := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + ctx: generalCtx, // General context is cancelled + watchUntilReadyCtx: nil, // Method context is nil, should fall back + } + + objs := getRuntimeObjFromManifests(t, []string{podCompleteManifest}) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + require.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + + err := sw.WatchUntilReady(resourceList, time.Second*3) + // Should fail due to cancelled general context + require.Error(t, err) + assert.Contains(t, err.Error(), "context canceled") + }) + + t.Run("Wait falls back to general context when method context is nil", func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + ) + + // Create a cancelled general context + generalCtx, generalCancel := context.WithCancel(context.Background()) + generalCancel() // Cancel immediately + + sw := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + ctx: generalCtx, // General context is cancelled + waitCtx: nil, // Method context is nil, should fall back + } + + objs := getRuntimeObjFromManifests(t, []string{podCurrentManifest}) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + require.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + + err := sw.Wait(resourceList, time.Second*3) + // Should fail due to cancelled general context + require.Error(t, err) + assert.Contains(t, err.Error(), "context canceled") + }) + + t.Run("WaitWithJobs falls back to general context when method context is nil", func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + batchv1.SchemeGroupVersion.WithKind("Job"), + ) + + // Create a cancelled general context + generalCtx, generalCancel := context.WithCancel(context.Background()) + generalCancel() // Cancel immediately + + sw := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + ctx: generalCtx, // General context is cancelled + waitWithJobsCtx: nil, // Method context is nil, should fall back + } + + objs := getRuntimeObjFromManifests(t, []string{jobCompleteManifest}) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + require.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + + err := sw.WaitWithJobs(resourceList, time.Second*3) + // Should fail due to cancelled general context + require.Error(t, err) + assert.Contains(t, err.Error(), "context canceled") + }) + + t.Run("WaitForDelete falls back to general context when method context is nil", func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + ) + + // Create a cancelled general context + generalCtx, generalCancel := context.WithCancel(context.Background()) + generalCancel() // Cancel immediately + + sw := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + ctx: generalCtx, // General context is cancelled + waitForDeleteCtx: nil, // Method context is nil, should fall back + } + + objs := getRuntimeObjFromManifests(t, []string{podCurrentManifest}) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + require.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + + err := sw.WaitForDelete(resourceList, time.Second*3) + // Should fail due to cancelled general context + require.Error(t, err) + assert.Contains(t, err.Error(), "context canceled") + }) +} + +func TestMethodContextOverridesGeneralContext(t *testing.T) { + t.Parallel() + + t.Run("method-specific context overrides general context for WatchUntilReady", func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + ) + + // General context is cancelled, but method context is not + generalCtx, generalCancel := context.WithCancel(context.Background()) + generalCancel() + + sw := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + ctx: generalCtx, // Cancelled + watchUntilReadyCtx: context.Background(), // Not cancelled - should be used + } + + objs := getRuntimeObjFromManifests(t, []string{podCompleteManifest}) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + require.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + + err := sw.WatchUntilReady(resourceList, time.Second*3) + // Should succeed because method context is used and it's not cancelled + assert.NoError(t, err) + }) + + t.Run("method-specific context overrides general context for Wait", func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + ) + + // General context is cancelled, but method context is not + generalCtx, generalCancel := context.WithCancel(context.Background()) + generalCancel() + + sw := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + ctx: generalCtx, // Cancelled + waitCtx: context.Background(), // Not cancelled - should be used + } + + objs := getRuntimeObjFromManifests(t, []string{podCurrentManifest}) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + require.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + + err := sw.Wait(resourceList, time.Second*3) + // Should succeed because method context is used and it's not cancelled + assert.NoError(t, err) + }) + + t.Run("method-specific context overrides general context for WaitWithJobs", func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + batchv1.SchemeGroupVersion.WithKind("Job"), + ) + + // General context is cancelled, but method context is not + generalCtx, generalCancel := context.WithCancel(context.Background()) + generalCancel() + + sw := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + ctx: generalCtx, // Cancelled + waitWithJobsCtx: context.Background(), // Not cancelled - should be used + } + + objs := getRuntimeObjFromManifests(t, []string{jobCompleteManifest}) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + require.NoError(t, err) + } + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + + err := sw.WaitWithJobs(resourceList, time.Second*3) + // Should succeed because method context is used and it's not cancelled + assert.NoError(t, err) + }) + + t.Run("method-specific context overrides general context for WaitForDelete", func(t *testing.T) { + t.Parallel() + c := newTestClient(t) + timeout := time.Second + timeUntilPodDelete := time.Millisecond * 500 + fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) + fakeMapper := testutil.NewFakeRESTMapper( + v1.SchemeGroupVersion.WithKind("Pod"), + ) + + // General context is cancelled, but method context is not + generalCtx, generalCancel := context.WithCancel(context.Background()) + generalCancel() + + sw := statusWaiter{ + client: fakeClient, + restMapper: fakeMapper, + ctx: generalCtx, // Cancelled + waitForDeleteCtx: context.Background(), // Not cancelled - should be used + } + + objs := getRuntimeObjFromManifests(t, []string{podCurrentManifest}) + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) + require.NoError(t, err) + } + + // Schedule deletion + for _, obj := range objs { + u := obj.(*unstructured.Unstructured) + gvr := getGVR(t, fakeMapper, u) + go func(gvr schema.GroupVersionResource, u *unstructured.Unstructured) { + time.Sleep(timeUntilPodDelete) + err := fakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName()) + assert.NoError(t, err) + }(gvr, u) + } + + resourceList := getResourceListFromRuntimeObjs(t, c, objs) + err := sw.WaitForDelete(resourceList, timeout) + // Should succeed because method context is used and it's not cancelled + assert.NoError(t, err) + }) +} + func TestWatchUntilReadyWithCustomReaders(t *testing.T) { t.Parallel() tests := []struct { From 63b40a7a5e0d3f00ef2b4c1de9f50fb7d6df4ead Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Thu, 22 Jan 2026 09:12:53 -0500 Subject: [PATCH 150/571] use logger with waiter Signed-off-by: Austin Abro --- pkg/kube/client.go | 7 ++-- pkg/kube/statuswait.go | 20 ++++++----- pkg/kube/statuswait_test.go | 71 +++++++++++++++++++++---------------- 3 files changed, 56 insertions(+), 42 deletions(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index ad8f851d1..c4ea1e596 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -166,7 +166,8 @@ func (c *Client) newStatusWatcher(opts ...WaitOption) (*statusWaiter, error) { if waitContext == nil { waitContext = c.WaitContext } - return &statusWaiter{ + + sw := &statusWaiter{ restMapper: restMapper, client: dynamicClient, ctx: waitContext, @@ -175,7 +176,9 @@ func (c *Client) newStatusWatcher(opts ...WaitOption) (*statusWaiter, error) { waitWithJobsCtx: o.waitWithJobsCtx, waitForDeleteCtx: o.waitForDeleteCtx, readers: o.statusReaders, - }, nil + } + sw.SetLogger(c.Logger().Handler()) + return sw, nil } func (c *Client) GetWaiter(ws WaitStrategy) (Waiter, error) { diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index 84819492b..01024afa6 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -38,6 +38,7 @@ import ( "k8s.io/client-go/dynamic" watchtools "k8s.io/client-go/tools/watch" + "helm.sh/helm/v4/internal/logging" helmStatusReaders "helm.sh/helm/v4/internal/statusreaders" ) @@ -50,6 +51,7 @@ type statusWaiter struct { waitWithJobsCtx context.Context waitForDeleteCtx context.Context readers []engine.StatusReader + logging.LogHolder } // DefaultStatusWatcherTimeout is the timeout used by the status waiter when a @@ -72,7 +74,7 @@ func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.D } ctx, cancel := w.contextWithTimeout(w.watchUntilReadyCtx, timeout) defer cancel() - slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) + w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) jobSR := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) podSR := helmStatusReaders.NewCustomPodStatusReader(w.restMapper) @@ -94,7 +96,7 @@ func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) er } ctx, cancel := w.contextWithTimeout(w.waitCtx, timeout) defer cancel() - slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) + w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) sw.StatusReader = statusreaders.NewStatusReader(w.restMapper, w.readers...) return w.wait(ctx, resourceList, sw) @@ -106,7 +108,7 @@ func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Dura } ctx, cancel := w.contextWithTimeout(w.waitWithJobsCtx, timeout) defer cancel() - slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) + w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) newCustomJobStatusReader := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) readers := append([]engine.StatusReader(nil), w.readers...) @@ -122,7 +124,7 @@ func (w *statusWaiter) WaitForDelete(resourceList ResourceList, timeout time.Dur } ctx, cancel := w.contextWithTimeout(w.waitForDeleteCtx, timeout) defer cancel() - slog.Debug("waiting for resources to be deleted", "count", len(resourceList), "timeout", timeout) + w.Logger().Debug("waiting for resources to be deleted", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) return w.waitForDelete(ctx, resourceList, sw) } @@ -142,7 +144,7 @@ func (w *statusWaiter) waitForDelete(ctx context.Context, resourceList ResourceL RESTScopeStrategy: watcher.RESTScopeNamespace, }) statusCollector := collector.NewResourceStatusCollector(resources) - done := statusCollector.ListenWithObserver(eventCh, statusObserver(cancel, status.NotFoundStatus)) + done := statusCollector.ListenWithObserver(eventCh, statusObserver(cancel, status.NotFoundStatus, w.Logger())) <-done if statusCollector.Error != nil { @@ -189,7 +191,7 @@ func (w *statusWaiter) wait(ctx context.Context, resourceList ResourceList, sw w RESTScopeStrategy: watcher.RESTScopeNamespace, }) statusCollector := collector.NewResourceStatusCollector(resources) - done := statusCollector.ListenWithObserver(eventCh, statusObserver(cancel, status.CurrentStatus)) + done := statusCollector.ListenWithObserver(eventCh, statusObserver(cancel, status.CurrentStatus, w.Logger())) <-done if statusCollector.Error != nil { @@ -228,7 +230,7 @@ func contextWithTimeout(ctx context.Context, timeout time.Duration) (context.Con return watchtools.ContextWithOptionalTimeout(ctx, timeout) } -func statusObserver(cancel context.CancelFunc, desired status.Status) collector.ObserverFunc { +func statusObserver(cancel context.CancelFunc, desired status.Status, logger *slog.Logger) collector.ObserverFunc { return func(statusCollector *collector.ResourceStatusCollector, _ event.Event) { var rss []*event.ResourceStatus var nonDesiredResources []*event.ResourceStatus @@ -253,7 +255,7 @@ func statusObserver(cancel context.CancelFunc, desired status.Status) collector. } if aggregator.AggregateStatus(rss, desired) == desired { - slog.Debug("all resources achieved desired status", "desiredStatus", desired, "resourceCount", len(rss)) + logger.Debug("all resources achieved desired status", "desiredStatus", desired, "resourceCount", len(rss)) cancel() return } @@ -264,7 +266,7 @@ func statusObserver(cancel context.CancelFunc, desired status.Status) collector. return nonDesiredResources[i].Identifier.Name < nonDesiredResources[j].Identifier.Name }) first := nonDesiredResources[0] - slog.Debug("waiting for resource", "namespace", first.Identifier.Namespace, "name", first.Identifier.Name, "kind", first.Identifier.GroupKind.Kind, "expectedStatus", desired, "actualStatus", first.Status) + logger.Debug("waiting for resource", "namespace", first.Identifier.Namespace, "name", first.Identifier.Name, "kind", first.Identifier.GroupKind.Kind, "expectedStatus", desired, "actualStatus", first.Status) } } } diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index 83aaa357a..d2dd57872 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "log/slog" "strings" "sync/atomic" "testing" @@ -326,6 +327,7 @@ func TestStatusWaitForDelete(t *testing.T) { restMapper: fakeMapper, client: fakeClient, } + statusWaiter.SetLogger(slog.Default().Handler()) objsToCreate := getRuntimeObjFromManifests(t, tt.manifestsToCreate) for _, objToCreate := range objsToCreate { u := objToCreate.(*unstructured.Unstructured) @@ -369,6 +371,7 @@ func TestStatusWaitForDeleteNonExistentObject(t *testing.T) { restMapper: fakeMapper, client: fakeClient, } + statusWaiter.SetLogger(slog.Default().Handler()) // Don't create the object to test that the wait for delete works when the object doesn't exist objManifest := getRuntimeObjFromManifests(t, []string{podCurrentManifest}) resourceList := getResourceListFromRuntimeObjs(t, c, objManifest) @@ -425,6 +428,7 @@ func TestStatusWait(t *testing.T) { client: fakeClient, restMapper: fakeMapper, } + statusWaiter.SetLogger(slog.Default().Handler()) objs := getRuntimeObjFromManifests(t, tt.objManifests) for _, obj := range objs { u := obj.(*unstructured.Unstructured) @@ -481,6 +485,7 @@ func TestWaitForJobComplete(t *testing.T) { client: fakeClient, restMapper: fakeMapper, } + statusWaiter.SetLogger(slog.Default().Handler()) objs := getRuntimeObjFromManifests(t, tt.objManifests) for _, obj := range objs { u := obj.(*unstructured.Unstructured) @@ -543,6 +548,7 @@ func TestWatchForReady(t *testing.T) { client: fakeClient, restMapper: fakeMapper, } + statusWaiter.SetLogger(slog.Default().Handler()) objs := getRuntimeObjFromManifests(t, tt.objManifests) for _, obj := range objs { u := obj.(*unstructured.Unstructured) @@ -570,19 +576,19 @@ func TestStatusWaitMultipleNamespaces(t *testing.T) { name string objManifests []string expectErrStrs []string - testFunc func(statusWaiter, ResourceList, time.Duration) error + testFunc func(*statusWaiter, ResourceList, time.Duration) error }{ { name: "pods in multiple namespaces", objManifests: []string{podNamespace1Manifest, podNamespace2Manifest}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, }, { name: "hooks in multiple namespaces", objManifests: []string{jobNamespace1CompleteManifest, podNamespace2SucceededManifest}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.WatchUntilReady(rl, timeout) }, }, @@ -590,42 +596,42 @@ func TestStatusWaitMultipleNamespaces(t *testing.T) { name: "error when resource not ready in one namespace", objManifests: []string{podNamespace1NoStatusManifest, podNamespace2Manifest}, expectErrStrs: []string{"resource Pod/namespace-1/pod-ns1 not ready. status: InProgress", "context deadline exceeded"}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, }, { name: "delete resources in multiple namespaces", objManifests: []string{podNamespace1Manifest, podNamespace2Manifest}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.WaitForDelete(rl, timeout) }, }, { name: "cluster-scoped resources work correctly with unrestricted permissions", objManifests: []string{podNamespace1Manifest, clusterRoleManifest}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, }, { name: "namespace-scoped and cluster-scoped resources work together", objManifests: []string{podNamespace1Manifest, podNamespace2Manifest, clusterRoleManifest}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, }, { name: "delete cluster-scoped resources works correctly", objManifests: []string{podNamespace1Manifest, namespaceManifest}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.WaitForDelete(rl, timeout) }, }, { name: "watch cluster-scoped resources works correctly", objManifests: []string{clusterRoleManifest}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.WatchUntilReady(rl, timeout) }, }, @@ -646,6 +652,7 @@ func TestStatusWaitMultipleNamespaces(t *testing.T) { client: fakeClient, restMapper: fakeMapper, } + sw.SetLogger(slog.Default().Handler()) objs := getRuntimeObjFromManifests(t, tt.objManifests) for _, obj := range objs { u := obj.(*unstructured.Unstructured) @@ -668,7 +675,7 @@ func TestStatusWaitMultipleNamespaces(t *testing.T) { } resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := tt.testFunc(sw, resourceList, time.Second*3) + err := tt.testFunc(&sw, resourceList, time.Second*3) if tt.expectErrStrs != nil { require.Error(t, err) for _, expectedErrStr := range tt.expectErrStrs { @@ -756,13 +763,13 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { objManifests []string allowedNamespaces []string expectErrs []error - testFunc func(statusWaiter, ResourceList, time.Duration) error + testFunc func(*statusWaiter, ResourceList, time.Duration) error }{ { name: "pods in multiple namespaces with namespace permissions", objManifests: []string{podNamespace1Manifest, podNamespace2Manifest}, allowedNamespaces: []string{"namespace-1", "namespace-2"}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, }, @@ -770,7 +777,7 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { name: "delete pods in multiple namespaces with namespace permissions", objManifests: []string{podNamespace1Manifest, podNamespace2Manifest}, allowedNamespaces: []string{"namespace-1", "namespace-2"}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.WaitForDelete(rl, timeout) }, }, @@ -778,7 +785,7 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { name: "hooks in multiple namespaces with namespace permissions", objManifests: []string{jobNamespace1CompleteManifest, podNamespace2SucceededManifest}, allowedNamespaces: []string{"namespace-1", "namespace-2"}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.WatchUntilReady(rl, timeout) }, }, @@ -787,7 +794,7 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { objManifests: []string{podNamespace1Manifest, clusterRoleManifest}, allowedNamespaces: []string{"namespace-1"}, expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, }, @@ -796,7 +803,7 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { objManifests: []string{podNamespace1Manifest, namespaceManifest}, allowedNamespaces: []string{"namespace-1"}, expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.WaitForDelete(rl, timeout) }, }, @@ -805,7 +812,7 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { objManifests: []string{podNamespace1Manifest, podNamespace2Manifest}, allowedNamespaces: []string{"namespace-1"}, expectErrs: []error{fmt.Errorf("user does not have LIST permissions in namespace %q", "namespace-2")}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, }, @@ -827,6 +834,7 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { client: baseFakeClient, restMapper: fakeMapper, } + sw.SetLogger(slog.Default().Handler()) objs := getRuntimeObjFromManifests(t, tt.objManifests) for _, obj := range objs { u := obj.(*unstructured.Unstructured) @@ -849,7 +857,7 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { } resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := tt.testFunc(sw, resourceList, time.Second*3) + err := tt.testFunc(&sw, resourceList, time.Second*3) if tt.expectErrs != nil { require.Error(t, err) for _, expectedErr := range tt.expectErrs { @@ -870,13 +878,13 @@ func TestStatusWaitMixedResources(t *testing.T) { objManifests []string allowedNamespaces []string expectErrs []error - testFunc func(statusWaiter, ResourceList, time.Duration) error + testFunc func(*statusWaiter, ResourceList, time.Duration) error }{ { name: "wait succeeds with namespace-scoped resources only", objManifests: []string{podNamespace1Manifest, podNamespace2Manifest}, allowedNamespaces: []string{"namespace-1", "namespace-2"}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, }, @@ -885,7 +893,7 @@ func TestStatusWaitMixedResources(t *testing.T) { objManifests: []string{podNamespace1Manifest, clusterRoleManifest}, allowedNamespaces: []string{"namespace-1"}, expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, }, @@ -894,7 +902,7 @@ func TestStatusWaitMixedResources(t *testing.T) { objManifests: []string{podNamespace1Manifest, clusterRoleManifest}, allowedNamespaces: []string{"namespace-1"}, expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.WaitForDelete(rl, timeout) }, }, @@ -903,7 +911,7 @@ func TestStatusWaitMixedResources(t *testing.T) { objManifests: []string{podNamespace1Manifest, namespaceManifest}, allowedNamespaces: []string{"namespace-1"}, expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, }, @@ -912,7 +920,7 @@ func TestStatusWaitMixedResources(t *testing.T) { objManifests: []string{podNamespace1Manifest, podNamespace2Manifest}, allowedNamespaces: []string{"namespace-1"}, expectErrs: []error{fmt.Errorf("user does not have LIST permissions in namespace %q", "namespace-2")}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, }, @@ -934,6 +942,7 @@ func TestStatusWaitMixedResources(t *testing.T) { client: baseFakeClient, restMapper: fakeMapper, } + sw.SetLogger(slog.Default().Handler()) objs := getRuntimeObjFromManifests(t, tt.objManifests) for _, obj := range objs { u := obj.(*unstructured.Unstructured) @@ -956,7 +965,7 @@ func TestStatusWaitMixedResources(t *testing.T) { } resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := tt.testFunc(sw, resourceList, time.Second*3) + err := tt.testFunc(&sw, resourceList, time.Second*3) if tt.expectErrs != nil { require.Error(t, err) for _, expectedErr := range tt.expectErrs { @@ -1151,7 +1160,7 @@ func TestStatusWaitWithFailedResources(t *testing.T) { objManifests []string customReader *mockStatusReader expectErrStrs []string - testFunc func(statusWaiter, ResourceList, time.Duration) error + testFunc func(*statusWaiter, ResourceList, time.Duration) error }{ { name: "Wait returns error when resource has failed", @@ -1161,7 +1170,7 @@ func TestStatusWaitWithFailedResources(t *testing.T) { status: status.FailedStatus, }, expectErrStrs: []string{"resource Pod/ns/in-progress-pod not ready. status: Failed, message: mock status reader"}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, }, @@ -1172,7 +1181,7 @@ func TestStatusWaitWithFailedResources(t *testing.T) { expectErrStrs: []string{ "resource Job/default/failed-job not ready. status: Failed", }, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.WaitWithJobs(rl, timeout) }, }, @@ -1188,7 +1197,7 @@ func TestStatusWaitWithFailedResources(t *testing.T) { "resource Pod/ns/in-progress-pod not ready. status: Failed, message: mock status reader", "resource Pod/ns/current-pod not ready. status: Failed, message: mock status reader", }, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, }, @@ -1201,7 +1210,7 @@ func TestStatusWaitWithFailedResources(t *testing.T) { }, // WatchUntilReady also waits for CurrentStatus, so failed resources should return error expectErrStrs: []string{"resource Pod/ns/in-progress-pod not ready. status: Failed, message: mock status reader"}, - testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error { + testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.WatchUntilReady(rl, timeout) }, }, @@ -1233,7 +1242,7 @@ func TestStatusWaitWithFailedResources(t *testing.T) { assert.NoError(t, err) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := tt.testFunc(sw, resourceList, time.Second*3) + err := tt.testFunc(&sw, resourceList, time.Second*3) if tt.expectErrStrs != nil { require.Error(t, err) for _, expectedErrStr := range tt.expectErrStrs { From e3829ebbbb833e159926c6193e474eb9d067ef75 Mon Sep 17 00:00:00 2001 From: Philipp Born Date: Thu, 22 Jan 2026 18:27:40 +0100 Subject: [PATCH 151/571] fix(copystructure): handle nil elements in slice copying When copying slices containing nil interface{} elements, the copyValue function would panic with 'reflect: call of reflect.Value.Set on zero Value'. This occurred because reflect.ValueOf(nil) returns a zero Value that cannot be set. This issue was introduced in v4.1.0 when replacing mitchellh/copystructure with an internal implementation. The fix mirrors the existing nil handling logic used for map values. Fixes helm template panic when processing charts with YAML like: extraArgs: - Added test case to verify slice elements with nil values are properly handled during deep copy operations. Signed-off-by: Philipp Born --- internal/copystructure/copystructure.go | 10 +++++++++- internal/copystructure/copystructure_test.go | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/internal/copystructure/copystructure.go b/internal/copystructure/copystructure.go index aa5510298..c55897aaa 100644 --- a/internal/copystructure/copystructure.go +++ b/internal/copystructure/copystructure.go @@ -89,7 +89,15 @@ func copyValue(original reflect.Value) (any, error) { } copied := reflect.MakeSlice(original.Type(), original.Len(), original.Cap()) for i := 0; i < original.Len(); i++ { - val, err := copyValue(original.Index(i)) + elem := original.Index(i) + + // Handle nil values in slices (e.g., interface{} elements that are nil) + if elem.Kind() == reflect.Interface && elem.IsNil() { + copied.Index(i).Set(elem) + continue + } + + val, err := copyValue(elem) if err != nil { return nil, err } diff --git a/internal/copystructure/copystructure_test.go b/internal/copystructure/copystructure_test.go index d1708dc75..b21af6460 100644 --- a/internal/copystructure/copystructure_test.go +++ b/internal/copystructure/copystructure_test.go @@ -113,6 +113,21 @@ func TestCopy_Slice(t *testing.T) { input[0]["key1"] = "modified" assert.Equal(t, "value1", resultSlice[0]["key1"]) }) + + t.Run("slice with nil elements", func(t *testing.T) { + input := []any{ + "value1", + nil, + "value2", + } + result, err := Copy(input) + require.NoError(t, err) + + resultSlice, ok := result.([]any) + require.True(t, ok) + assert.Equal(t, input, resultSlice) + assert.Nil(t, resultSlice[1]) + }) } func TestCopy_Map(t *testing.T) { From ec0726523e52448eb05c8b5b3faae969e3a79266 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Thu, 22 Jan 2026 15:45:29 -0500 Subject: [PATCH 152/571] whitespace Signed-off-by: Austin Abro --- pkg/kube/client.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index c4ea1e596..fc706496d 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -166,7 +166,6 @@ func (c *Client) newStatusWatcher(opts ...WaitOption) (*statusWaiter, error) { if waitContext == nil { waitContext = c.WaitContext } - sw := &statusWaiter{ restMapper: restMapper, client: dynamicClient, From b59e533b7675122631e0733adbfd6b35dd3515a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 21:33:28 +0000 Subject: [PATCH 153/571] chore(deps): bump actions/checkout from 6.0.1 to 6.0.2 Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.1 to 6.0.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/8e8c483db84b4bee98b60c0593521ed34d9990e8...de0fac2e4500dabe0009e67214ff5f5447ce83dd) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 77e5d9343..f2c472faa 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0f3fe6d8f..b66bbc29d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -43,7 +43,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index ede3b4c71..142cb3533 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index b1a50b553..1bfc81b66 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 with: persist-credentials: false - name: Add variables to environment file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 46c999191..017687cc1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest-16-cores steps: - name: Checkout source code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 with: fetch-depth: 0 @@ -85,7 +85,7 @@ jobs: if: github.ref == 'refs/heads/main' && github.repository == 'helm/helm' steps: - name: Checkout source code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # pin@v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 514a649cb..dc2d3b3d9 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -28,7 +28,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false From b53198e7eee04dab651c15cb7b3b6b77dd92553c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 21:34:35 +0000 Subject: [PATCH 154/571] chore(deps): bump github.com/fluxcd/cli-utils Bumps [github.com/fluxcd/cli-utils](https://github.com/fluxcd/cli-utils) from 0.37.0-flux.1 to 0.37.1-flux.1. - [Commits](https://github.com/fluxcd/cli-utils/compare/v0.37.0-flux.1...v0.37.1-flux.1) --- updated-dependencies: - dependency-name: github.com/fluxcd/cli-utils dependency-version: 0.37.1-flux.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4b0bb8977..9d5c73aaf 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.18.0 - github.com/fluxcd/cli-utils v0.37.0-flux.1 + github.com/fluxcd/cli-utils v0.37.1-flux.1 github.com/foxcpp/go-mockdns v1.2.0 github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.13.0 diff --git a/go.sum b/go.sum index 6e010e4b2..9846d01e6 100644 --- a/go.sum +++ b/go.sum @@ -91,8 +91,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/cli-utils v0.37.0-flux.1 h1:k/VvPNT3tGa/l2N+qzHduaQr3GVbgoWS6nw7tGZz16w= -github.com/fluxcd/cli-utils v0.37.0-flux.1/go.mod h1:aND5wX3LuTFtB7eUT7vsWr8mmxRVSPR2Wkvbn0SqPfw= +github.com/fluxcd/cli-utils v0.37.1-flux.1 h1:WnG2mHxCPZMj/soIq/S/1zvbrGCJN3GJGbNfG06X55M= +github.com/fluxcd/cli-utils v0.37.1-flux.1/go.mod h1:aND5wX3LuTFtB7eUT7vsWr8mmxRVSPR2Wkvbn0SqPfw= github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= From 6d5f56fa6e7c8e4462d80895fcce87b926e4b8ce Mon Sep 17 00:00:00 2001 From: Scott Rigby Date: Thu, 22 Jan 2026 16:52:59 -0500 Subject: [PATCH 155/571] fix(template): deprecate unused --hide-notes and --render-subchart-notes flags The --hide-notes and --render-subchart-notes flags have no effect for `helm template` since template output never includes notes. Mark these flags as deprecated and hide them from help output. The flags are preserved for backwards compatibility in Helm 4 and will be removed in Helm 5. Fixes #31752 Signed-off-by: Scott Rigby --- pkg/cmd/install.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index d36cd9e34..9ca901d76 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -209,6 +209,26 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates") f.BoolVar(&client.HideNotes, "hide-notes", false, "if set, do not show notes in install output. Does not affect presence in chart metadata") f.BoolVar(&client.TakeOwnership, "take-ownership", false, "if set, install will ignore the check for helm annotations and take ownership of the existing resources") + + // For `helm template`, these notes flags are legacy, unused, and should not show in help, but + // must remain accepted for backwards compatibility in Helm 4. Deprecate and hide them for now + // TODO remove these from template command in Helm 5 + if cmd.Name() == "template" { + if err := cmd.Flags().MarkDeprecated("hide-notes", "this flag has no effect for 'helm template' and will be removed in Helm 5"); err != nil { + log.Fatal(err) + } + if err := cmd.Flags().MarkHidden("hide-notes"); err != nil { + log.Fatal(err) + } + + if err := cmd.Flags().MarkDeprecated("render-subchart-notes", "this flag has no effect for 'helm template' and will be removed in Helm 5"); err != nil { + log.Fatal(err) + } + if err := cmd.Flags().MarkHidden("render-subchart-notes"); err != nil { + log.Fatal(err) + } + } + addValueOptionsFlags(f, valueOpts) addChartPathOptionsFlags(f, &client.ChartPathOptions) AddWaitFlag(cmd, &client.WaitStrategy) From becf9bf7e33867a3f26affac34e9d51e277767bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:56:47 +0000 Subject: [PATCH 156/571] chore(deps): bump sigs.k8s.io/controller-runtime from 0.23.0 to 0.23.1 Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.23.0 to 0.23.1. - [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.23.0...v0.23.1) --- updated-dependencies: - dependency-name: sigs.k8s.io/controller-runtime dependency-version: 0.23.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 4b0bb8977..e8900ab77 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( k8s.io/klog/v2 v2.130.1 k8s.io/kubectl v0.35.0 oras.land/oras-go/v2 v2.6.0 - sigs.k8s.io/controller-runtime v0.23.0 + sigs.k8s.io/controller-runtime v0.23.1 sigs.k8s.io/kustomize/kyaml v0.21.0 sigs.k8s.io/yaml v1.6.0 ) @@ -177,5 +177,5 @@ require ( sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.20.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect ) diff --git a/go.sum b/go.sum index 6e010e4b2..0cd184dfb 100644 --- a/go.sum +++ b/go.sum @@ -502,8 +502,8 @@ k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzk k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= -sigs.k8s.io/controller-runtime v0.23.0 h1:Ubi7klJWiwEWqDY+odSVZiFA0aDSevOCXpa38yCSYu8= -sigs.k8s.io/controller-runtime v0.23.0/go.mod h1:DBOIr9NsprUqCZ1ZhsuJ0wAnQSIxY/C6VjZbmLgw0j0= +sigs.k8s.io/controller-runtime v0.23.1 h1:TjJSM80Nf43Mg21+RCy3J70aj/W6KyvDtOlpKf+PupE= +sigs.k8s.io/controller-runtime v0.23.1/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= @@ -512,7 +512,7 @@ sigs.k8s.io/kustomize/kyaml v0.21.0 h1:7mQAf3dUwf0wBerWJd8rXhVcnkk5Tvn/q91cGkaP6 sigs.k8s.io/kustomize/kyaml v0.21.0/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= From 787b61cedb933d22011e1da1368d0e615ea60ffe Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Thu, 29 Jan 2026 06:04:56 +0000 Subject: [PATCH 157/571] Revert "Consider GroupVersionKind when matching resources" Signed-off-by: Matheus Pimenta --- pkg/kube/resource.go | 11 +++++++++-- pkg/kube/resource_test.go | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pkg/kube/resource.go b/pkg/kube/resource.go index d88b171f0..d6d08d589 100644 --- a/pkg/kube/resource.go +++ b/pkg/kube/resource.go @@ -79,7 +79,14 @@ func (r ResourceList) Intersect(rs ResourceList) ResourceList { return r.Filter(rs.Contains) } -// isMatchingInfo returns true if infos match on Name and GroupVersionKind. +// isMatchingInfo returns true if infos match on Name, Namespace, Group and Kind. +// +// IMPORTANT: Version is intentionally excluded from the comparison. Resources +// served by the same CRD at different API versions (e.g. v2beta1 vs v2beta2) +// share the same underlying storage in the Kubernetes API server. Comparing +// the full GroupVersionKind causes Difference() to treat a version change as +// a resource removal + addition, which makes Helm delete the resource it just +// created during upgrades. See https://github.com/helm/helm/issues/31768 func isMatchingInfo(a, b *resource.Info) bool { - return a.Name == b.Name && a.Namespace == b.Namespace && a.Mapping.GroupVersionKind == b.Mapping.GroupVersionKind + return a.Name == b.Name && a.Namespace == b.Namespace && a.Mapping.GroupVersionKind.GroupKind() == b.Mapping.GroupVersionKind.GroupKind() } diff --git a/pkg/kube/resource_test.go b/pkg/kube/resource_test.go index ccc613c1b..41215a878 100644 --- a/pkg/kube/resource_test.go +++ b/pkg/kube/resource_test.go @@ -72,8 +72,8 @@ func TestIsMatchingInfo(t *testing.T) { gvkDiffVersion := schema.GroupVersionKind{Group: "group1", Version: "diff", Kind: "pod"} resourceInfoDiffVersion := resource.Info{Name: "name1", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvkDiffVersion}} - if isMatchingInfo(&resourceInfo, &resourceInfoDiffVersion) { - t.Error("expected resources not equal") + if !isMatchingInfo(&resourceInfo, &resourceInfoDiffVersion) { + t.Error("expected resources with different versions but same group and kind to be equal") } gvkDiffKind := schema.GroupVersionKind{Group: "group1", Version: "version1", Kind: "deployment"} From 9c9c3a6b5c0f1cd1e4c4e9f002aa411c58dd656a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 21:34:16 +0000 Subject: [PATCH 158/571] chore(deps): bump github.com/lib/pq from 1.10.9 to 1.11.1 Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.10.9 to 1.11.1. - [Release notes](https://github.com/lib/pq/releases) - [Changelog](https://github.com/lib/pq/blob/master/CHANGELOG.md) - [Commits](https://github.com/lib/pq/compare/v1.10.9...v1.11.1) --- updated-dependencies: - dependency-name: github.com/lib/pq dependency-version: 1.11.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 0a3e34d1e..92de0e40f 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/gofrs/flock v0.13.0 github.com/gosuri/uitable v0.0.4 github.com/jmoiron/sqlx v1.4.0 - github.com/lib/pq v1.10.9 + github.com/lib/pq v1.11.1 github.com/mattn/go-shellwords v1.0.12 github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 diff --git a/go.sum b/go.sum index 6380deb76..46e86d3e3 100644 --- a/go.sum +++ b/go.sum @@ -191,8 +191,9 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI= +github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= From d983696e354a9e0605cbb3034937dc84af42995c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20T=C3=B4rres?= Date: Thu, 29 Jan 2026 17:30:14 -0800 Subject: [PATCH 159/571] fix pulling charts from OCI indices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pedro Tôrres --- pkg/registry/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 750bb9715..72ba0b540 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -566,6 +566,7 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { // Build allowed media types for chart pull allowedMediaTypes := []string{ + ocispec.MediaTypeImageIndex, ocispec.MediaTypeImageManifest, ConfigMediaType, } From e8f386b5aac232c114a036598c2e3015fe296edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20T=C3=B4rres?= Date: Thu, 29 Jan 2026 19:47:37 -0800 Subject: [PATCH 160/571] add image index test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pedro Tôrres --- pkg/registry/client_http_test.go | 7 ++ pkg/registry/registry_test.go | 170 +++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) diff --git a/pkg/registry/client_http_test.go b/pkg/registry/client_http_test.go index a2c3a1833..546d837d5 100644 --- a/pkg/registry/client_http_test.go +++ b/pkg/registry/client_http_test.go @@ -73,6 +73,13 @@ func (suite *HTTPRegistryClientTestSuite) Test_4_ManInTheMiddle() { suite.True(errors.Is(err, content.ErrMismatchedDigest)) } +func (suite *HTTPRegistryClientTestSuite) Test_5_ImageIndex() { + ref := fmt.Sprintf("%s/testrepo/image-index:0.1.0", suite.FakeRegistryHost) + + _, err := suite.RegistryClient.Pull(ref) + suite.Nil(err) +} + func TestHTTPRegistryClientTestSuite(t *testing.T) { suite.Run(t, new(HTTPRegistryClientTestSuite)) } diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index d4921c50b..c82b165bc 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -35,6 +35,7 @@ import ( "github.com/distribution/distribution/v3/registry" _ "github.com/distribution/distribution/v3/registry/auth/htpasswd" _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "golang.org/x/crypto/bcrypt" @@ -60,6 +61,7 @@ var ( type TestRegistry struct { suite.Suite Out io.Writer + FakeRegistryHost string DockerRegistryHost string CompromisedRegistryHost string WorkspaceDir string @@ -159,6 +161,7 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool) { suite.dockerRegistry, err = registry.NewRegistry(context.Background(), config) suite.Nil(err, "no error creating test registry") + suite.FakeRegistryHost = initFakeRegistryTestServer() suite.CompromisedRegistryHost = initCompromisedRegistryTestServer() go func() { _ = suite.dockerRegistry.ListenAndServe() @@ -209,6 +212,173 @@ func initCompromisedRegistryTestServer() string { return fmt.Sprintf("localhost:%s", u.Port()) } +func initFakeRegistryTestServer() string { + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/v2/testrepo/image-index/manifests/0.1.0": + w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex) + w.Write([]byte(`{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", + "manifests": [ + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:2771e37a12b7bcb2902456ecf3f29bf9ee11ec348e66e8eb322d9780ad7fc2df", + "size": 1035, + "platform": { + "architecture": "amd64", + "os": "linux" + }, + "annotations": { + "com.docker.official-images.bashbrew.arch": "amd64", + "org.opencontainers.image.base.name": "scratch", + "org.opencontainers.image.created": "2025-08-13T22:16:57Z", + "org.opencontainers.image.revision": "6930d60e10e81283a57be3ee3a2b5ca328a40304", + "org.opencontainers.image.source": "https://github.com/docker-library/hello-world.git#6930d60e10e81283a57be3ee3a2b5ca328a40304:amd64/hello-world", + "org.opencontainers.image.url": "https://hub.docker.com/_/hello-world", + "org.opencontainers.image.version": "linux" + } + }, + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:6b75187531c5e9b6a85c8946d5d82e4ef3801e051fbff338f382f3edfa60e3d2", + "size": 566, + "platform": { + "architecture": "unknown", + "os": "unknown" + }, + "annotations": { + "com.docker.official-images.bashbrew.arch": "amd64", + "vnd.docker.reference.digest": "sha256:2771e37a12b7bcb2902456ecf3f29bf9ee11ec348e66e8eb322d9780ad7fc2df", + "vnd.docker.reference.type": "attestation-manifest" + } + }, + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:7fbdc47de56b45d092f8f419e8b6183adf0159d00e05574c01787231b54fe28f", + "size": 815 + } + ] +}`)) + + case "/v2/testrepo/image-index/manifests/sha256:2771e37a12b7bcb2902456ecf3f29bf9ee11ec348e66e8eb322d9780ad7fc2df": + w.Header().Set("Content-Type", ocispec.MediaTypeImageManifest) + w.Write([]byte(`{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "digest": "sha256:1b44b5a3e06a9aae883e7bf25e45c100be0bb81a0e01b32de604f3ac44711634", + "size": 547 + }, + "layers": [ + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "digest": "sha256:17eec7bbc9d79fa397ac95c7283ecd04d1fe6978516932a3db110c6206430809", + "size": 2380 + } + ], + "annotations": { + "com.docker.official-images.bashbrew.arch": "amd64", + "org.opencontainers.image.base.name": "scratch", + "org.opencontainers.image.created": "2025-08-08T19:05:17Z", + "org.opencontainers.image.revision": "6930d60e10e81283a57be3ee3a2b5ca328a40304", + "org.opencontainers.image.source": "https://github.com/docker-library/hello-world.git#6930d60e10e81283a57be3ee3a2b5ca328a40304:amd64/hello-world", + "org.opencontainers.image.url": "https://hub.docker.com/_/hello-world", + "org.opencontainers.image.version": "linux" + } +}`)) + + case "/v2/testrepo/image-index/manifests/sha256:6b75187531c5e9b6a85c8946d5d82e4ef3801e051fbff338f382f3edfa60e3d2": + w.Header().Set("Content-Type", ocispec.MediaTypeImageManifest) + w.Write([]byte(`{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "digest": "sha256:ec4b6233950725be4c816667d1eb2782ad59dc65b12f7ac53f1ffa0ad5b95b5b", + "size": 167 + }, + "layers": [ + { + "mediaType": "application/vnd.in-toto+json", + "digest": "sha256:ea52d2000f90ad63267302cba134025ee586b07a63c47aa9467471a395aee6c2", + "size": 4822, + "annotations": { + "in-toto.io/predicate-type": "https://slsa.dev/provenance/v0.2" + } + } + ] +}`)) + + case "/v2/testrepo/image-index/manifests/sha256:7fbdc47de56b45d092f8f419e8b6183adf0159d00e05574c01787231b54fe28f": + w.Header().Set("Content-Type", ocispec.MediaTypeImageManifest) + w.Write([]byte(`{ + "schemaVersion": 2, + "config": { + "mediaType": "application/vnd.cncf.helm.config.v1+json", + "digest": "sha256:24de43e4a9f5ed9427479f27dd7bab9d158227abe593302a6f54d1e13a903ac3", + "size": 112 + }, + "layers": [ + { + "mediaType": "application/vnd.cncf.helm.chart.provenance.v1.prov", + "digest": "sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256", + "size": 695 + }, + { + "mediaType": "application/vnd.cncf.helm.chart.content.v1.tar+gzip", + "digest": "sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55", + "size": 973 + } + ], + "annotations": { + "org.opencontainers.image.description": "A Helm chart for Kubernetes", + "org.opencontainers.image.title": "signtest", + "org.opencontainers.image.version": "0.1.0" + } +}`)) + + case "/v2/testrepo/image-index/blobs/sha256:24de43e4a9f5ed9427479f27dd7bab9d158227abe593302a6f54d1e13a903ac3": + w.Header().Set("Content-Type", ConfigMediaType) + w.Write([]byte(`{ + "name":"signtest", + "version":"0.1.0", + "description":"A Helm chart for Kubernetes", + "apiVersion":"v1" +}`)) + + case "/v2/testrepo/image-index/blobs/sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256": + data, err := os.ReadFile("../downloader/testdata/signtest-0.1.0.tgz.prov") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Header().Set("Content-Type", ProvLayerMediaType) + w.Write(data) + + case "/v2/testrepo/image-index/blobs/sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55": + data, err := os.ReadFile("../downloader/testdata/signtest-0.1.0.tgz") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Header().Set("Content-Type", ChartLayerMediaType) + w.Write(data) + + default: + w.WriteHeader(http.StatusNotFound) + } + })) + + u, _ := url.Parse(s.URL) + return fmt.Sprintf("localhost:%s", u.Port()) +} + func testPush(suite *TestRegistry) { testingChartCreationTime := "1977-09-02T22:04:05Z" From 111d4e6e0e86af6ba25a355be1a7599f5258ee58 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 6 Feb 2026 16:10:41 +0100 Subject: [PATCH 161/571] chore(pkg): fix modernize linter #### Description fix modernize linter in pkg/chart/common/util Signed-off-by: Matthieu MOREL --- pkg/action/upgrade.go | 12 ++++++------ pkg/chart/common.go | 18 +++++++++--------- pkg/chart/common/util/jsonschema_test.go | 18 +++++++++--------- pkg/chart/common/util/values_test.go | 14 +++++++------- pkg/chart/v2/lint/rules/values_test.go | 18 +++++++++--------- pkg/chart/v2/loader/load.go | 14 +++++++------- pkg/engine/lookup_func.go | 14 +++++++------- 7 files changed, 54 insertions(+), 54 deletions(-) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 4c93855b1..4c5a5b55e 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -156,13 +156,13 @@ func (u *Upgrade) SetRegistryClient(client *registry.Client) { } // Run executes the upgrade on the given release. -func (u *Upgrade) Run(name string, chart chart.Charter, vals map[string]interface{}) (ri.Releaser, error) { +func (u *Upgrade) Run(name string, chart chart.Charter, vals map[string]any) (ri.Releaser, error) { ctx := context.Background() return u.RunWithContext(ctx, name, chart, vals) } // RunWithContext executes the upgrade on the given release with context. -func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Charter, vals map[string]interface{}) (ri.Releaser, error) { +func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Charter, vals map[string]any) (ri.Releaser, error) { if err := u.cfg.KubeClient.IsReachable(); err != nil { return nil, err } @@ -213,7 +213,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Char } // prepareUpgrade builds an upgraded release for an upgrade operation. -func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[string]interface{}) (*release.Release, *release.Release, bool, error) { +func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[string]any) (*release.Release, *release.Release, bool, error) { if chart == nil { return nil, nil, false, errMissingChart } @@ -408,7 +408,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR } rChan := make(chan resultMessage) ctxChan := make(chan resultMessage) - doneChan := make(chan interface{}) + doneChan := make(chan any) defer close(doneChan) go u.releasingUpgrade(rChan, upgradedRelease, current, target, originalRelease, serverSideApply) go u.handleContext(ctx, doneChan, ctxChan, upgradedRelease) @@ -434,7 +434,7 @@ func (u *Upgrade) reportToPerformUpgrade(c chan<- resultMessage, rel *release.Re } // Setup listener for SIGINT and SIGTERM -func (u *Upgrade) handleContext(ctx context.Context, done chan interface{}, c chan<- resultMessage, upgradedRelease *release.Release) { +func (u *Upgrade) handleContext(ctx context.Context, done chan any, c chan<- resultMessage, upgradedRelease *release.Release) { select { case <-ctx.Done(): err := ctx.Err() @@ -601,7 +601,7 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e // // This is skipped if the u.ResetValues flag is set, in which case the // request values are not altered. -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]any) (map[string]any, error) { if u.ResetValues { // If ResetValues is set, we completely ignore current.Config. u.cfg.Logger().Debug("resetting values to the chart's original version") diff --git a/pkg/chart/common.go b/pkg/chart/common.go index cd87e91e7..288227953 100644 --- a/pkg/chart/common.go +++ b/pkg/chart/common.go @@ -56,8 +56,8 @@ func (r *v2Accessor) IsRoot() bool { return r.chrt.IsRoot() } -func (r *v2Accessor) MetadataAsMap() map[string]interface{} { - var ret map[string]interface{} +func (r *v2Accessor) MetadataAsMap() map[string]any { + var ret map[string]any if r.chrt.Metadata == nil { return ret } @@ -101,7 +101,7 @@ func (r *v2Accessor) MetaDependencies() []Dependency { return deps } -func (r *v2Accessor) Values() map[string]interface{} { +func (r *v2Accessor) Values() map[string]any { return r.chrt.Values } @@ -125,8 +125,8 @@ func (r *v3Accessor) IsRoot() bool { return r.chrt.IsRoot() } -func (r *v3Accessor) MetadataAsMap() map[string]interface{} { - var ret map[string]interface{} +func (r *v3Accessor) MetadataAsMap() map[string]any { + var ret map[string]any if r.chrt.Metadata == nil { return ret } @@ -170,7 +170,7 @@ func (r *v3Accessor) MetaDependencies() []Dependency { return deps } -func (r *v3Accessor) Values() map[string]interface{} { +func (r *v3Accessor) Values() map[string]any { return r.chrt.Values } @@ -182,7 +182,7 @@ func (r *v3Accessor) Deprecated() bool { return r.chrt.Metadata.Deprecated } -func structToMap(obj interface{}) (map[string]interface{}, error) { +func structToMap(obj any) (map[string]any, error) { objValue := reflect.ValueOf(obj) // If the value is a pointer, dereference it @@ -195,7 +195,7 @@ func structToMap(obj interface{}) (map[string]interface{}, error) { return nil, fmt.Errorf("input must be a struct or a pointer to a struct") } - result := make(map[string]interface{}) + result := make(map[string]any) objType := objValue.Type() for i := 0; i < objValue.NumField(); i++ { @@ -221,7 +221,7 @@ func structToMap(obj interface{}) (map[string]interface{}, error) { result[field.Name] = nestedMap } case reflect.Slice: - sliceOfMaps := make([]interface{}, value.Len()) + sliceOfMaps := make([]any, value.Len()) for j := 0; j < value.Len(); j++ { sliceElement := value.Index(j) if sliceElement.Kind() == reflect.Struct || sliceElement.Kind() == reflect.Pointer { diff --git a/pkg/chart/common/util/jsonschema_test.go b/pkg/chart/common/util/jsonschema_test.go index 834b1faf6..24073175c 100644 --- a/pkg/chart/common/util/jsonschema_test.go +++ b/pkg/chart/common/util/jsonschema_test.go @@ -138,9 +138,9 @@ func TestValidateAgainstSchema(t *testing.T) { } chrt.AddDependency(subchart) - vals := map[string]interface{}{ + vals := map[string]any{ "name": "John", - "subchart": map[string]interface{}{ + "subchart": map[string]any{ "age": 25, }, } @@ -165,9 +165,9 @@ func TestValidateAgainstSchemaNegative(t *testing.T) { } chrt.AddDependency(subchart) - vals := map[string]interface{}{ + vals := map[string]any{ "name": "John", - "subchart": map[string]interface{}{}, + "subchart": map[string]any{}, } var errString string @@ -200,9 +200,9 @@ func TestValidateAgainstSchema2020(t *testing.T) { } chrt.AddDependency(subchart) - vals := map[string]interface{}{ + vals := map[string]any{ "name": "John", - "subchart": map[string]interface{}{ + "subchart": map[string]any{ "data": []any{"hello", 12}, }, } @@ -227,9 +227,9 @@ func TestValidateAgainstSchema2020Negative(t *testing.T) { } chrt.AddDependency(subchart) - vals := map[string]interface{}{ + vals := map[string]any{ "name": "John", - "subchart": map[string]interface{}{ + "subchart": map[string]any{ "data": []any{12}, }, } @@ -294,7 +294,7 @@ func TestValidateAgainstSingleSchema_UnresolvedURN_Ignored(t *testing.T) { "$schema": "https://json-schema.org/draft-07/schema#", "$ref": "urn:example:helm:schemas:v1:helm-schema-validation-conditions:v1/helmSchemaValidation-true" }`) - vals := map[string]interface{}{"any": "value"} + vals := map[string]any{"any": "value"} if err := ValidateAgainstSingleSchema(vals, schema); err != nil { t.Fatalf("expected no error when URN unresolved is ignored, got: %v", err) } diff --git a/pkg/chart/common/util/values_test.go b/pkg/chart/common/util/values_test.go index 706d3cfda..1dea04132 100644 --- a/pkg/chart/common/util/values_test.go +++ b/pkg/chart/common/util/values_test.go @@ -26,17 +26,17 @@ import ( func TestToRenderValues(t *testing.T) { - chartValues := map[string]interface{}{ + chartValues := map[string]any{ "name": "al Rashid", - "where": map[string]interface{}{ + "where": map[string]any{ "city": "Basrah", "title": "caliph", }, } - overrideValues := map[string]interface{}{ + overrideValues := map[string]any{ "name": "Haroun", - "where": map[string]interface{}{ + "where": map[string]any{ "city": "Baghdad", "date": "809 CE", }, @@ -67,11 +67,11 @@ func TestToRenderValues(t *testing.T) { } // Ensure that the top-level values are all set. - metamap := res["Chart"].(map[string]interface{}) + metamap := res["Chart"].(map[string]any) if name := metamap["Name"]; name.(string) != "test" { t.Errorf("Expected chart name 'test', got %q", name) } - relmap := res["Release"].(map[string]interface{}) + relmap := res["Release"].(map[string]any) if name := relmap["Name"]; name.(string) != "Seven Voyages" { t.Errorf("Expected release name 'Seven Voyages', got %q", name) } @@ -98,7 +98,7 @@ func TestToRenderValues(t *testing.T) { if vals["name"] != "Haroun" { t.Errorf("Expected 'Haroun', got %q (%v)", vals["name"], vals) } - where := vals["where"].(map[string]interface{}) + where := vals["where"].(map[string]any) expects := map[string]string{ "city": "Baghdad", "date": "809 CE", diff --git a/pkg/chart/v2/lint/rules/values_test.go b/pkg/chart/v2/lint/rules/values_test.go index 288b77436..afc544ebd 100644 --- a/pkg/chart/v2/lint/rules/values_test.go +++ b/pkg/chart/v2/lint/rules/values_test.go @@ -67,7 +67,7 @@ func TestValidateValuesFileWellFormed(t *testing.T) { ` tmpdir := ensure.TempFile(t, "values.yaml", []byte(badYaml)) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}, false); err == nil { + if err := validateValuesFile(valfile, map[string]any{}, false); err == nil { t.Fatal("expected values file to fail parsing") } } @@ -78,7 +78,7 @@ func TestValidateValuesFileSchema(t *testing.T) { createTestingSchema(t, tmpdir) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}, false); err != nil { + if err := validateValuesFile(valfile, map[string]any{}, false); err != nil { t.Fatalf("Failed validation with %s", err) } } @@ -91,7 +91,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) { valfile := filepath.Join(tmpdir, "values.yaml") - err := validateValuesFile(valfile, map[string]interface{}{}, false) + err := validateValuesFile(valfile, map[string]any{}, false) if err == nil { t.Fatal("expected values file to fail parsing") } @@ -107,7 +107,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T valfile := filepath.Join(tmpdir, "values.yaml") - err := validateValuesFile(valfile, map[string]interface{}{}, true) + err := validateValuesFile(valfile, map[string]any{}, true) if err != nil { t.Fatal("expected values file to pass parsing because of skipSchemaValidation") } @@ -115,7 +115,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T func TestValidateValuesFileSchemaOverrides(t *testing.T) { yaml := "username: admin" - overrides := map[string]interface{}{ + overrides := map[string]any{ "password": "swordfish", } tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml)) @@ -131,24 +131,24 @@ func TestValidateValuesFile(t *testing.T) { tests := []struct { name string yaml string - overrides map[string]interface{} + overrides map[string]any errorMessage string }{ { name: "value added", yaml: "username: admin", - overrides: map[string]interface{}{"password": "swordfish"}, + overrides: map[string]any{"password": "swordfish"}, }, { name: "value not overridden", yaml: "username: admin\npassword:", - overrides: map[string]interface{}{"username": "anotherUser"}, + overrides: map[string]any{"username": "anotherUser"}, errorMessage: "- at '/password': got null, want string", }, { name: "value overridden", yaml: "username: admin\npassword:", - overrides: map[string]interface{}{"username": "anotherUser", "password": "swordfish"}, + overrides: map[string]any{"username": "anotherUser", "password": "swordfish"}, }, } diff --git a/pkg/chart/v2/loader/load.go b/pkg/chart/v2/loader/load.go index d466e247c..e3c5ce325 100644 --- a/pkg/chart/v2/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -209,11 +209,11 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { // // The reader is expected to contain one or more YAML documents, the values of which are merged. // And the values can be either a chart's default values or user-supplied values. -func LoadValues(data io.Reader) (map[string]interface{}, error) { - values := map[string]interface{}{} +func LoadValues(data io.Reader) (map[string]any, error) { + values := map[string]any{} reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) for { - currentMap := map[string]interface{}{} + currentMap := map[string]any{} raw, err := reader.Read() if err != nil { if errors.Is(err, io.EOF) { @@ -231,13 +231,13 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) { // MergeMaps merges two maps. If a key exists in both maps, the value from b will be used. // If the value is a map, the maps will be merged recursively. -func MergeMaps(a, b map[string]interface{}) map[string]interface{} { - out := make(map[string]interface{}, len(a)) +func MergeMaps(a, b map[string]any) map[string]any { + out := make(map[string]any, len(a)) maps.Copy(out, a) for k, v := range b { - if v, ok := v.(map[string]interface{}); ok { + if v, ok := v.(map[string]any); ok { if bv, ok := out[k]; ok { - if bv, ok := bv.(map[string]interface{}); ok { + if bv, ok := bv.(map[string]any); ok { out[k] = MergeMaps(bv, v) continue } diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go index c6ad8d252..52b6ffdaf 100644 --- a/pkg/engine/lookup_func.go +++ b/pkg/engine/lookup_func.go @@ -30,7 +30,7 @@ import ( "k8s.io/client-go/rest" ) -type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) +type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]any, error) // NewLookupFunction returns a function for looking up objects in the cluster. // @@ -55,11 +55,11 @@ func (c clientProviderFromConfig) GetClientFor(apiVersion, kind string) (dynamic } func newLookupFunction(clientProvider ClientProvider) lookupFunc { - return func(apiversion string, kind string, namespace string, name string) (map[string]interface{}, error) { + return func(apiversion string, kind string, namespace string, name string) (map[string]any, error) { var client dynamic.ResourceInterface c, namespaced, err := clientProvider.GetClientFor(apiversion, kind) if err != nil { - return map[string]interface{}{}, err + return map[string]any{}, err } if namespaced && namespace != "" { client = c.Namespace(namespace) @@ -73,9 +73,9 @@ func newLookupFunction(clientProvider ClientProvider) lookupFunc { if apierrors.IsNotFound(err) { // Just return an empty interface when the object was not found. // That way, users can use `if not (lookup ...)` in their templates. - return map[string]interface{}{}, nil + return map[string]any{}, nil } - return map[string]interface{}{}, err + return map[string]any{}, err } return obj.UnstructuredContent(), nil } @@ -85,9 +85,9 @@ func newLookupFunction(clientProvider ClientProvider) lookupFunc { if apierrors.IsNotFound(err) { // Just return an empty interface when the object was not found. // That way, users can use `if not (lookup ...)` in their templates. - return map[string]interface{}{}, nil + return map[string]any{}, nil } - return map[string]interface{}{}, err + return map[string]any{}, err } return obj.UnstructuredContent(), nil } From e2d184c79e9049c19bcc466bfe1289ccc6b73717 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 6 Feb 2026 16:10:51 +0100 Subject: [PATCH 162/571] chore(internal): fix modernize linter #### Description fix modernize linter in internal/chart/v3/lint/rules Signed-off-by: Matthieu MOREL --- internal/chart/v3/lint/rules/chartfile.go | 10 ++++---- internal/chart/v3/lint/rules/template.go | 6 ++--- internal/chart/v3/lint/rules/values.go | 6 ++--- internal/chart/v3/lint/rules/values_test.go | 18 ++++++------- internal/chart/v3/loader/load.go | 14 +++++------ internal/chart/v3/loader/load_test.go | 24 +++++++++--------- internal/chart/v3/util/dependencies.go | 28 ++++++++++----------- 7 files changed, 53 insertions(+), 53 deletions(-) diff --git a/internal/chart/v3/lint/rules/chartfile.go b/internal/chart/v3/lint/rules/chartfile.go index fc246ba80..f171b56b9 100644 --- a/internal/chart/v3/lint/rules/chartfile.go +++ b/internal/chart/v3/lint/rules/chartfile.go @@ -69,15 +69,15 @@ func Chartfile(linter *support.Linter) { linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartDependencies(chartFile)) } -func validateChartVersionType(data map[string]interface{}) error { +func validateChartVersionType(data map[string]any) error { return isStringValue(data, "version") } -func validateChartAppVersionType(data map[string]interface{}) error { +func validateChartAppVersionType(data map[string]any) error { return isStringValue(data, "appVersion") } -func isStringValue(data map[string]interface{}, key string) error { +func isStringValue(data map[string]any, key string) error { value, ok := data[key] if !ok { return nil @@ -214,12 +214,12 @@ func validateChartType(cf *chart.Metadata) error { // loadChartFileForTypeCheck loads the Chart.yaml // in a generic form of a map[string]interface{}, so that the type // of the values can be checked -func loadChartFileForTypeCheck(filename string) (map[string]interface{}, error) { +func loadChartFileForTypeCheck(filename string) (map[string]any, error) { b, err := os.ReadFile(filename) if err != nil { return nil, err } - y := make(map[string]interface{}) + y := make(map[string]any) err = yaml.Unmarshal(b, &y) return y, err } diff --git a/internal/chart/v3/lint/rules/template.go b/internal/chart/v3/lint/rules/template.go index 38e602b7e..35e4940ab 100644 --- a/internal/chart/v3/lint/rules/template.go +++ b/internal/chart/v3/lint/rules/template.go @@ -42,17 +42,17 @@ import ( ) // Templates lints the templates in the Linter. -func Templates(linter *support.Linter, values map[string]interface{}, namespace string, _ bool) { +func Templates(linter *support.Linter, values map[string]any, namespace string, _ bool) { TemplatesWithKubeVersion(linter, values, namespace, nil) } // TemplatesWithKubeVersion lints the templates in the Linter, allowing to specify the kubernetes version. -func TemplatesWithKubeVersion(linter *support.Linter, values map[string]interface{}, namespace string, kubeVersion *common.KubeVersion) { +func TemplatesWithKubeVersion(linter *support.Linter, values map[string]any, namespace string, kubeVersion *common.KubeVersion) { TemplatesWithSkipSchemaValidation(linter, values, namespace, kubeVersion, false) } // TemplatesWithSkipSchemaValidation lints the templates in the Linter, allowing to specify the kubernetes version and if schema validation is enabled or not. -func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string]interface{}, namespace string, kubeVersion *common.KubeVersion, skipSchemaValidation bool) { +func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string]any, namespace string, kubeVersion *common.KubeVersion, skipSchemaValidation bool) { fpath := "templates/" templatesPath := filepath.Join(linter.ChartDir, fpath) diff --git a/internal/chart/v3/lint/rules/values.go b/internal/chart/v3/lint/rules/values.go index 0af9765dd..ba371cbe2 100644 --- a/internal/chart/v3/lint/rules/values.go +++ b/internal/chart/v3/lint/rules/values.go @@ -32,7 +32,7 @@ import ( // they are only tested for well-formedness. // // If additional values are supplied, they are coalesced into the values in values.yaml. -func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]interface{}, skipSchemaValidation bool) { +func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]any, skipSchemaValidation bool) { file := "values.yaml" vf := filepath.Join(linter.ChartDir, file) fileExists := linter.RunLinterRule(support.InfoSev, file, validateValuesFileExistence(vf)) @@ -52,7 +52,7 @@ func validateValuesFileExistence(valuesPath string) error { return nil } -func validateValuesFile(valuesPath string, overrides map[string]interface{}, skipSchemaValidation bool) error { +func validateValuesFile(valuesPath string, overrides map[string]any, skipSchemaValidation bool) error { values, err := common.ReadValuesFile(valuesPath) if err != nil { return fmt.Errorf("unable to parse YAML: %w", err) @@ -63,7 +63,7 @@ func validateValuesFile(valuesPath string, overrides map[string]interface{}, ski // We could change that. For now, though, we retain that strategy, and thus can // coalesce tables (like reuse-values does) instead of doing the full chart // CoalesceValues - coalescedValues := util.CoalesceTables(make(map[string]interface{}, len(overrides)), overrides) + coalescedValues := util.CoalesceTables(make(map[string]any, len(overrides)), overrides) coalescedValues = util.CoalesceTables(coalescedValues, values) ext := filepath.Ext(valuesPath) diff --git a/internal/chart/v3/lint/rules/values_test.go b/internal/chart/v3/lint/rules/values_test.go index 288b77436..afc544ebd 100644 --- a/internal/chart/v3/lint/rules/values_test.go +++ b/internal/chart/v3/lint/rules/values_test.go @@ -67,7 +67,7 @@ func TestValidateValuesFileWellFormed(t *testing.T) { ` tmpdir := ensure.TempFile(t, "values.yaml", []byte(badYaml)) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}, false); err == nil { + if err := validateValuesFile(valfile, map[string]any{}, false); err == nil { t.Fatal("expected values file to fail parsing") } } @@ -78,7 +78,7 @@ func TestValidateValuesFileSchema(t *testing.T) { createTestingSchema(t, tmpdir) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}, false); err != nil { + if err := validateValuesFile(valfile, map[string]any{}, false); err != nil { t.Fatalf("Failed validation with %s", err) } } @@ -91,7 +91,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) { valfile := filepath.Join(tmpdir, "values.yaml") - err := validateValuesFile(valfile, map[string]interface{}{}, false) + err := validateValuesFile(valfile, map[string]any{}, false) if err == nil { t.Fatal("expected values file to fail parsing") } @@ -107,7 +107,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T valfile := filepath.Join(tmpdir, "values.yaml") - err := validateValuesFile(valfile, map[string]interface{}{}, true) + err := validateValuesFile(valfile, map[string]any{}, true) if err != nil { t.Fatal("expected values file to pass parsing because of skipSchemaValidation") } @@ -115,7 +115,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T func TestValidateValuesFileSchemaOverrides(t *testing.T) { yaml := "username: admin" - overrides := map[string]interface{}{ + overrides := map[string]any{ "password": "swordfish", } tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml)) @@ -131,24 +131,24 @@ func TestValidateValuesFile(t *testing.T) { tests := []struct { name string yaml string - overrides map[string]interface{} + overrides map[string]any errorMessage string }{ { name: "value added", yaml: "username: admin", - overrides: map[string]interface{}{"password": "swordfish"}, + overrides: map[string]any{"password": "swordfish"}, }, { name: "value not overridden", yaml: "username: admin\npassword:", - overrides: map[string]interface{}{"username": "anotherUser"}, + overrides: map[string]any{"username": "anotherUser"}, errorMessage: "- at '/password': got null, want string", }, { name: "value overridden", yaml: "username: admin\npassword:", - overrides: map[string]interface{}{"username": "anotherUser", "password": "swordfish"}, + overrides: map[string]any{"username": "anotherUser", "password": "swordfish"}, }, } diff --git a/internal/chart/v3/loader/load.go b/internal/chart/v3/loader/load.go index 373c4659f..48f346ccf 100644 --- a/internal/chart/v3/loader/load.go +++ b/internal/chart/v3/loader/load.go @@ -182,11 +182,11 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { // // The reader is expected to contain one or more YAML documents, the values of which are merged. // And the values can be either a chart's default values or user-supplied values. -func LoadValues(data io.Reader) (map[string]interface{}, error) { - values := map[string]interface{}{} +func LoadValues(data io.Reader) (map[string]any, error) { + values := map[string]any{} reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) for { - currentMap := map[string]interface{}{} + currentMap := map[string]any{} raw, err := reader.Read() if err != nil { if errors.Is(err, io.EOF) { @@ -204,13 +204,13 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) { // MergeMaps merges two maps. If a key exists in both maps, the value from b will be used. // If the value is a map, the maps will be merged recursively. -func MergeMaps(a, b map[string]interface{}) map[string]interface{} { - out := make(map[string]interface{}, len(a)) +func MergeMaps(a, b map[string]any) map[string]any { + out := make(map[string]any, len(a)) maps.Copy(out, a) for k, v := range b { - if v, ok := v.(map[string]interface{}); ok { + if v, ok := v.(map[string]any); ok { if bv, ok := out[k]; ok { - if bv, ok := bv.(map[string]interface{}); ok { + if bv, ok := bv.(map[string]any); ok { out[k] = MergeMaps(bv, v) continue } diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index 12403f9c2..c32f79aff 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -455,7 +455,7 @@ func TestLoadInvalidArchive(t *testing.T) { func TestLoadValues(t *testing.T) { testCases := map[string]struct { data []byte - expctedValues map[string]interface{} + expctedValues map[string]any }{ "It should load values correctly": { data: []byte(` @@ -464,11 +464,11 @@ foo: bar: version: v2 `), - expctedValues: map[string]interface{}{ - "foo": map[string]interface{}{ + expctedValues: map[string]any{ + "foo": map[string]any{ "image": "foo:v1", }, - "bar": map[string]interface{}{ + "bar": map[string]any{ "version": "v2", }, }, @@ -483,11 +483,11 @@ bar: foo: image: foo:v2 `), - expctedValues: map[string]interface{}{ - "foo": map[string]interface{}{ + expctedValues: map[string]any{ + "foo": map[string]any{ "image": "foo:v2", }, - "bar": map[string]interface{}{ + "bar": map[string]any{ "version": "v2", }, }, @@ -507,24 +507,24 @@ foo: } func TestMergeValuesV3(t *testing.T) { - nestedMap := map[string]interface{}{ + nestedMap := map[string]any{ "foo": "bar", "baz": map[string]string{ "cool": "stuff", }, } - anotherNestedMap := map[string]interface{}{ + anotherNestedMap := map[string]any{ "foo": "bar", "baz": map[string]string{ "cool": "things", "awesome": "stuff", }, } - flatMap := map[string]interface{}{ + flatMap := map[string]any{ "foo": "bar", "baz": "stuff", } - anotherFlatMap := map[string]interface{}{ + anotherFlatMap := map[string]any{ "testing": "fun", } @@ -547,7 +547,7 @@ func TestMergeValuesV3(t *testing.T) { } testMap = MergeMaps(anotherFlatMap, anotherNestedMap) - expectedMap := map[string]interface{}{ + expectedMap := map[string]any{ "testing": "fun", "foo": "bar", "baz": map[string]string{ diff --git a/internal/chart/v3/util/dependencies.go b/internal/chart/v3/util/dependencies.go index 4ef9e6961..5bf8a593c 100644 --- a/internal/chart/v3/util/dependencies.go +++ b/internal/chart/v3/util/dependencies.go @@ -140,7 +140,7 @@ func copyMetadata(metadata *chart.Metadata) *chart.Metadata { } // processDependencyEnabled removes disabled charts from dependencies -func processDependencyEnabled(c *chart.Chart, v map[string]interface{}, path string) error { +func processDependencyEnabled(c *chart.Chart, v map[string]any, path string) error { if c.Metadata.Dependencies == nil { return nil } @@ -226,7 +226,7 @@ Loop: } // pathToMap creates a nested map given a YAML path in dot notation. -func pathToMap(path string, data map[string]interface{}) map[string]interface{} { +func pathToMap(path string, data map[string]any) map[string]any { if path == "." { return data } @@ -235,13 +235,13 @@ func pathToMap(path string, data map[string]interface{}) map[string]interface{} func parsePath(key string) []string { return strings.Split(key, ".") } -func set(path []string, data map[string]interface{}) map[string]interface{} { +func set(path []string, data map[string]any) map[string]any { if len(path) == 0 { return nil } cur := data for i := len(path) - 1; i >= 0; i-- { - cur = map[string]interface{}{path[i]: cur} + cur = map[string]any{path[i]: cur} } return cur } @@ -262,13 +262,13 @@ func processImportValues(c *chart.Chart, merge bool) error { if err != nil { return err } - b := make(map[string]interface{}) + b := make(map[string]any) // import values from each dependency if specified in import-values for _, r := range c.Metadata.Dependencies { - var outiv []interface{} + var outiv []any for _, riv := range r.ImportValues { switch iv := riv.(type) { - case map[string]interface{}: + case map[string]any: child := fmt.Sprintf("%v", iv["child"]) parent := fmt.Sprintf("%v", iv["parent"]) @@ -336,27 +336,27 @@ func processImportValues(c *chart.Chart, merge bool) error { return nil } -func deepCopyMap(vals map[string]interface{}) map[string]interface{} { +func deepCopyMap(vals map[string]any) map[string]any { valsCopy, err := copystructure.Copy(vals) if err != nil { return vals } - return valsCopy.(map[string]interface{}) + return valsCopy.(map[string]any) } -func trimNilValues(vals map[string]interface{}) map[string]interface{} { +func trimNilValues(vals map[string]any) map[string]any { valsCopy, err := copystructure.Copy(vals) if err != nil { return vals } - valsCopyMap := valsCopy.(map[string]interface{}) + valsCopyMap := valsCopy.(map[string]any) for key, val := range valsCopyMap { if val == nil { // Iterate over the values and remove nil keys delete(valsCopyMap, key) } else if istable(val) { // Recursively call into ourselves to remove keys from inner tables - valsCopyMap[key] = trimNilValues(val.(map[string]interface{})) + valsCopyMap[key] = trimNilValues(val.(map[string]any)) } } @@ -364,8 +364,8 @@ func trimNilValues(vals map[string]interface{}) map[string]interface{} { } // istable is a special-purpose function to see if the present thing matches the definition of a YAML table. -func istable(v interface{}) bool { - _, ok := v.(map[string]interface{}) +func istable(v any) bool { + _, ok := v.(map[string]any) return ok } From ba38159313d4f09280591ba7f860ef0523716220 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 6 Feb 2026 16:10:54 +0100 Subject: [PATCH 163/571] chore(pkg): fix modernize linter #### Description fix modernize linter in pkg/strvals Signed-off-by: Matthieu MOREL --- pkg/chart/common/util/coalesce.go | 62 +++---- pkg/chart/common/util/coalesce_test.go | 164 ++++++++--------- pkg/chart/v2/util/create.go | 2 +- pkg/strvals/literal_parser_test.go | 140 +++++++-------- pkg/strvals/parser_test.go | 232 ++++++++++++------------- 5 files changed, 300 insertions(+), 300 deletions(-) diff --git a/pkg/chart/common/util/coalesce.go b/pkg/chart/common/util/coalesce.go index 6c72b3d56..5994febbc 100644 --- a/pkg/chart/common/util/coalesce.go +++ b/pkg/chart/common/util/coalesce.go @@ -42,7 +42,7 @@ func concatPrefix(a, b string) string { // - Scalar values and arrays are replaced, maps are merged // - A chart has access to all of the variables for it, as well as all of // the values destined for its dependencies. -func CoalesceValues(chrt chart.Charter, vals map[string]interface{}) (common.Values, error) { +func CoalesceValues(chrt chart.Charter, vals map[string]any) (common.Values, error) { valsCopy, err := copyValues(vals) if err != nil { return vals, err @@ -64,7 +64,7 @@ func CoalesceValues(chrt chart.Charter, vals map[string]interface{}) (common.Val // Retaining Nils is useful when processes early in a Helm action or business // logic need to retain them for when Coalescing will happen again later in the // business logic. -func MergeValues(chrt chart.Charter, vals map[string]interface{}) (common.Values, error) { +func MergeValues(chrt chart.Charter, vals map[string]any) (common.Values, error) { valsCopy, err := copyValues(vals) if err != nil { return vals, err @@ -72,22 +72,22 @@ func MergeValues(chrt chart.Charter, vals map[string]interface{}) (common.Values return coalesce(log.Printf, chrt, valsCopy, "", true) } -func copyValues(vals map[string]interface{}) (common.Values, error) { +func copyValues(vals map[string]any) (common.Values, error) { v, err := copystructure.Copy(vals) if err != nil { return vals, err } - valsCopy := v.(map[string]interface{}) + valsCopy := v.(map[string]any) // if we have an empty map, make sure it is initialized if valsCopy == nil { - valsCopy = make(map[string]interface{}) + valsCopy = make(map[string]any) } return valsCopy, nil } -type printFn func(format string, v ...interface{}) +type printFn func(format string, v ...any) // coalesce coalesces the dest values and the chart values, giving priority to the dest values. // @@ -96,13 +96,13 @@ type printFn func(format string, v ...interface{}) // Note, the merge argument specifies whether this is being used by MergeValues // or CoalesceValues. Coalescing removes null values and their keys in some // situations while merging keeps the null values. -func coalesce(printf printFn, ch chart.Charter, dest map[string]interface{}, prefix string, merge bool) (map[string]interface{}, error) { +func coalesce(printf printFn, ch chart.Charter, dest map[string]any, prefix string, merge bool) (map[string]any, error) { coalesceValues(printf, ch, dest, prefix, merge) return coalesceDeps(printf, ch, dest, prefix, merge) } // coalesceDeps coalesces the dependencies of the given chart. -func coalesceDeps(printf printFn, chrt chart.Charter, dest map[string]interface{}, prefix string, merge bool) (map[string]interface{}, error) { +func coalesceDeps(printf printFn, chrt chart.Charter, dest map[string]any, prefix string, merge bool) (map[string]any, error) { ch, err := chart.NewAccessor(chrt) if err != nil { return dest, err @@ -114,12 +114,12 @@ func coalesceDeps(printf printFn, chrt chart.Charter, dest map[string]interface{ } if c, ok := dest[sub.Name()]; !ok { // If dest doesn't already have the key, create it. - dest[sub.Name()] = make(map[string]interface{}) + dest[sub.Name()] = make(map[string]any) } else if !istable(c) { return dest, fmt.Errorf("type mismatch on %s: %t", sub.Name(), c) } if dv, ok := dest[sub.Name()]; ok { - dvmap := dv.(map[string]interface{}) + dvmap := dv.(map[string]any) subPrefix := concatPrefix(prefix, ch.Name()) // Get globals out of dest and merge them into dvmap. coalesceGlobals(printf, dvmap, dest, subPrefix, merge) @@ -137,19 +137,19 @@ func coalesceDeps(printf printFn, chrt chart.Charter, dest map[string]interface{ // coalesceGlobals copies the globals out of src and merges them into dest. // // For convenience, returns dest. -func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix string, _ bool) { - var dg, sg map[string]interface{} +func coalesceGlobals(printf printFn, dest, src map[string]any, prefix string, _ bool) { + var dg, sg map[string]any if destglob, ok := dest[common.GlobalKey]; !ok { - dg = make(map[string]interface{}) - } else if dg, ok = destglob.(map[string]interface{}); !ok { + dg = make(map[string]any) + } else if dg, ok = destglob.(map[string]any); !ok { printf("warning: skipping globals because destination %s is not a table.", common.GlobalKey) return } if srcglob, ok := src[common.GlobalKey]; !ok { - sg = make(map[string]interface{}) - } else if sg, ok = srcglob.(map[string]interface{}); !ok { + sg = make(map[string]any) + } else if sg, ok = srcglob.(map[string]any); !ok { printf("warning: skipping globals because source %s is not a table.", common.GlobalKey) return } @@ -160,12 +160,12 @@ func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix st // tables in globals. for key, val := range sg { if istable(val) { - vv := copyMap(val.(map[string]interface{})) + vv := copyMap(val.(map[string]any)) if destv, ok := dg[key]; !ok { // Here there is no merge. We're just adding. dg[key] = vv } else { - if destvmap, ok := destv.(map[string]interface{}); !ok { + if destvmap, ok := destv.(map[string]any); !ok { printf("Conflict: cannot merge map onto non-map for %q. Skipping.", key) } else { // Basically, we reverse order of coalesce here to merge @@ -189,8 +189,8 @@ func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix st dest[common.GlobalKey] = dg } -func copyMap(src map[string]interface{}) map[string]interface{} { - m := make(map[string]interface{}, len(src)) +func copyMap(src map[string]any) map[string]any { + m := make(map[string]any, len(src)) maps.Copy(m, src) return m } @@ -198,7 +198,7 @@ func copyMap(src map[string]interface{}) map[string]interface{} { // coalesceValues builds up a values map for a particular chart. // // Values in v will override the values in the chart. -func coalesceValues(printf printFn, c chart.Charter, v map[string]interface{}, prefix string, merge bool) { +func coalesceValues(printf printFn, c chart.Charter, v map[string]any, prefix string, merge bool) { ch, err := chart.NewAccessor(c) if err != nil { return @@ -210,7 +210,7 @@ func coalesceValues(printf printFn, c chart.Charter, v map[string]interface{}, p // the original c.Values is altered. Creating a deep copy stops the problem. // This section is fault-tolerant as there is no ability to return an error. valuesCopy, err := copystructure.Copy(ch.Values()) - var vc map[string]interface{} + var vc map[string]any var ok bool if err != nil { // If there is an error something is wrong with copying c.Values it @@ -220,7 +220,7 @@ func coalesceValues(printf printFn, c chart.Charter, v map[string]interface{}, p printf("warning: unable to copy values, err: %s", err) vc = ch.Values() } else { - vc, ok = valuesCopy.(map[string]interface{}) + vc, ok = valuesCopy.(map[string]any) if !ok { // c.Values has a map[string]interface{} structure. If the copy of // it cannot be treated as map[string]interface{} there is something @@ -238,9 +238,9 @@ func coalesceValues(printf printFn, c chart.Charter, v map[string]interface{}, p // This allows Helm's various sources of values (value files or --set) to // remove incompatible keys from any previous chart, file, or set values. delete(v, key) - } else if dest, ok := value.(map[string]interface{}); ok { + } else if dest, ok := value.(map[string]any); ok { // if v[key] is a table, merge nv's val table into v[key]. - src, ok := val.(map[string]interface{}) + src, ok := val.(map[string]any) if !ok { // If the original value is nil, there is nothing to coalesce, so we don't print // the warning @@ -283,18 +283,18 @@ func childChartMergeTrue(chrt chart.Charter, key string, merge bool) bool { // CoalesceTables merges a source map into a destination map. // // dest is considered authoritative. -func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} { +func CoalesceTables(dst, src map[string]any) map[string]any { return coalesceTablesFullKey(log.Printf, dst, src, "", false) } -func MergeTables(dst, src map[string]interface{}) map[string]interface{} { +func MergeTables(dst, src map[string]any) map[string]any { return coalesceTablesFullKey(log.Printf, dst, src, "", true) } // coalesceTablesFullKey merges a source map into a destination map. // // dest is considered authoritative. -func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, prefix string, merge bool) map[string]interface{} { +func coalesceTablesFullKey(printf printFn, dst, src map[string]any, prefix string, merge bool) map[string]any { // When --reuse-values is set but there are no modifications yet, return new values if src == nil { return dst @@ -330,7 +330,7 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, pref dst[key] = val } else if istable(val) { if istable(dv) { - coalesceTablesFullKey(printf, dv.(map[string]interface{}), val.(map[string]interface{}), fullkey, merge) + coalesceTablesFullKey(printf, dv.(map[string]any), val.(map[string]any), fullkey, merge) } else { printf("warning: cannot overwrite table with non table for %s (%v)", fullkey, val) } @@ -342,7 +342,7 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, pref } // istable is a special-purpose function to see if the present thing matches the definition of a YAML table. -func istable(v interface{}) bool { - _, ok := v.(map[string]interface{}) +func istable(v any) bool { + _, ok := v.(map[string]any) return ok } diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index 4eaf4be2b..1d0baa84d 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -75,65 +75,65 @@ func TestCoalesceValues(t *testing.T) { c := withDeps(&chart.Chart{ Metadata: &chart.Metadata{Name: "moby"}, - Values: map[string]interface{}{ + Values: map[string]any{ "back": "exists", "bottom": "exists", "front": "exists", "left": "exists", "name": "moby", - "nested": map[string]interface{}{"boat": true}, + "nested": map[string]any{"boat": true}, "override": "bad", "right": "exists", "scope": "moby", "top": "nope", - "global": map[string]interface{}{ - "nested2": map[string]interface{}{"l0": "moby"}, + "global": map[string]any{ + "nested2": map[string]any{"l0": "moby"}, }, - "pequod": map[string]interface{}{ + "pequod": map[string]any{ "boat": "maybe", - "ahab": map[string]interface{}{ + "ahab": map[string]any{ "boat": "maybe", - "nested": map[string]interface{}{"boat": "maybe"}, + "nested": map[string]any{"boat": "maybe"}, }, }, }, }, withDeps(&chart.Chart{ Metadata: &chart.Metadata{Name: "pequod"}, - Values: map[string]interface{}{ + Values: map[string]any{ "name": "pequod", "scope": "pequod", - "global": map[string]interface{}{ - "nested2": map[string]interface{}{"l1": "pequod"}, + "global": map[string]any{ + "nested2": map[string]any{"l1": "pequod"}, }, "boat": false, - "ahab": map[string]interface{}{ + "ahab": map[string]any{ "boat": false, - "nested": map[string]interface{}{"boat": false}, + "nested": map[string]any{"boat": false}, }, }, }, &chart.Chart{ Metadata: &chart.Metadata{Name: "ahab"}, - Values: map[string]interface{}{ - "global": map[string]interface{}{ - "nested": map[string]interface{}{"foo": "bar", "foo2": "bar2"}, - "nested2": map[string]interface{}{"l2": "ahab"}, + Values: map[string]any{ + "global": map[string]any{ + "nested": map[string]any{"foo": "bar", "foo2": "bar2"}, + "nested2": map[string]any{"l2": "ahab"}, }, "scope": "ahab", "name": "ahab", "boat": true, - "nested": map[string]interface{}{"foo": false, "boat": true}, - "object": map[string]interface{}{"foo": "bar"}, + "nested": map[string]any{"foo": false, "boat": true}, + "object": map[string]any{"foo": "bar"}, }, }, ), &chart.Chart{ Metadata: &chart.Metadata{Name: "spouter"}, - Values: map[string]interface{}{ + Values: map[string]any{ "scope": "spouter", - "global": map[string]interface{}{ - "nested2": map[string]interface{}{"l1": "spouter"}, + "global": map[string]any{ + "nested2": map[string]any{"l1": "spouter"}, }, }, }, @@ -215,21 +215,21 @@ func TestCoalesceValues(t *testing.T) { } } - if _, ok := v["nested"].(map[string]interface{})["boat"]; ok { + if _, ok := v["nested"].(map[string]any)["boat"]; ok { t.Error("Expected nested boat key to be removed, still present") } - subchart := v["pequod"].(map[string]interface{}) + subchart := v["pequod"].(map[string]any) if _, ok := subchart["boat"]; ok { t.Error("Expected subchart boat key to be removed, still present") } - subsubchart := subchart["ahab"].(map[string]interface{}) + subsubchart := subchart["ahab"].(map[string]any) if _, ok := subsubchart["boat"]; ok { t.Error("Expected sub-subchart ahab boat key to be removed, still present") } - if _, ok := subsubchart["nested"].(map[string]interface{})["boat"]; ok { + if _, ok := subsubchart["nested"].(map[string]any)["boat"]; ok { t.Error("Expected sub-subchart nested boat key to be removed, still present") } @@ -241,7 +241,7 @@ func TestCoalesceValues(t *testing.T) { is.Equal(valsCopy, vals) } -func ttpl(tpl string, v map[string]interface{}) (string, error) { +func ttpl(tpl string, v map[string]any) (string, error) { var b bytes.Buffer tt := template.Must(template.New("t").Parse(tpl)) err := tt.Execute(&b, v) @@ -253,52 +253,52 @@ func TestMergeValues(t *testing.T) { c := withDeps(&chart.Chart{ Metadata: &chart.Metadata{Name: "moby"}, - Values: map[string]interface{}{ + Values: map[string]any{ "back": "exists", "bottom": "exists", "front": "exists", "left": "exists", "name": "moby", - "nested": map[string]interface{}{"boat": true}, + "nested": map[string]any{"boat": true}, "override": "bad", "right": "exists", "scope": "moby", "top": "nope", - "global": map[string]interface{}{ - "nested2": map[string]interface{}{"l0": "moby"}, + "global": map[string]any{ + "nested2": map[string]any{"l0": "moby"}, }, }, }, withDeps(&chart.Chart{ Metadata: &chart.Metadata{Name: "pequod"}, - Values: map[string]interface{}{ + Values: map[string]any{ "name": "pequod", "scope": "pequod", - "global": map[string]interface{}{ - "nested2": map[string]interface{}{"l1": "pequod"}, + "global": map[string]any{ + "nested2": map[string]any{"l1": "pequod"}, }, }, }, &chart.Chart{ Metadata: &chart.Metadata{Name: "ahab"}, - Values: map[string]interface{}{ - "global": map[string]interface{}{ - "nested": map[string]interface{}{"foo": "bar"}, - "nested2": map[string]interface{}{"l2": "ahab"}, + Values: map[string]any{ + "global": map[string]any{ + "nested": map[string]any{"foo": "bar"}, + "nested2": map[string]any{"l2": "ahab"}, }, "scope": "ahab", "name": "ahab", "boat": true, - "nested": map[string]interface{}{"foo": false, "bar": true}, + "nested": map[string]any{"foo": false, "bar": true}, }, }, ), &chart.Chart{ Metadata: &chart.Metadata{Name: "spouter"}, - Values: map[string]interface{}{ + Values: map[string]any{ "scope": "spouter", - "global": map[string]interface{}{ - "nested2": map[string]interface{}{"l1": "spouter"}, + "global": map[string]any{ + "nested2": map[string]any{"l1": "spouter"}, }, }, }, @@ -383,16 +383,16 @@ func TestMergeValues(t *testing.T) { } } - if _, ok := v["nested"].(map[string]interface{})["boat"]; !ok { + if _, ok := v["nested"].(map[string]any)["boat"]; !ok { t.Error("Expected nested boat key to be present but it was removed") } - subchart := v["pequod"].(map[string]interface{})["ahab"].(map[string]interface{}) + subchart := v["pequod"].(map[string]any)["ahab"].(map[string]any) if _, ok := subchart["boat"]; !ok { t.Error("Expected subchart boat key to be present but it was removed") } - if _, ok := subchart["nested"].(map[string]interface{})["bar"]; !ok { + if _, ok := subchart["nested"].(map[string]any)["bar"]; !ok { t.Error("Expected subchart nested bar key to be present but it was removed") } @@ -401,28 +401,28 @@ func TestMergeValues(t *testing.T) { } func TestCoalesceTables(t *testing.T) { - dst := map[string]interface{}{ + dst := map[string]any{ "name": "Ishmael", - "address": map[string]interface{}{ + "address": map[string]any{ "street": "123 Spouter Inn Ct.", "city": "Nantucket", "country": nil, }, - "details": map[string]interface{}{ + "details": map[string]any{ "friends": []string{"Tashtego"}, }, "boat": "pequod", "hole": nil, } - src := map[string]interface{}{ + src := map[string]any{ "occupation": "whaler", - "address": map[string]interface{}{ + "address": map[string]any{ "state": "MA", "street": "234 Spouter Inn Ct.", "country": "US", }, "details": "empty", - "boat": map[string]interface{}{ + "boat": map[string]any{ "mast": true, }, "hole": "black", @@ -439,7 +439,7 @@ func TestCoalesceTables(t *testing.T) { t.Errorf("Unexpected occupation: %s", dst["occupation"]) } - addr, ok := dst["address"].(map[string]interface{}) + addr, ok := dst["address"].(map[string]any) if !ok { t.Fatal("Address went away.") } @@ -460,7 +460,7 @@ func TestCoalesceTables(t *testing.T) { t.Error("The country is not left out.") } - if det, ok := dst["details"].(map[string]interface{}); !ok { + if det, ok := dst["details"].(map[string]any); !ok { t.Fatalf("Details is the wrong type: %v", dst["details"]) } else if _, ok := det["friends"]; !ok { t.Error("Could not find your friends. Maybe you don't have any. :-(") @@ -474,14 +474,14 @@ func TestCoalesceTables(t *testing.T) { t.Error("The hole still exists.") } - dst2 := map[string]interface{}{ + dst2 := map[string]any{ "name": "Ishmael", - "address": map[string]interface{}{ + "address": map[string]any{ "street": "123 Spouter Inn Ct.", "city": "Nantucket", "country": "US", }, - "details": map[string]interface{}{ + "details": map[string]any{ "friends": []string{"Tashtego"}, }, "boat": "pequod", @@ -496,7 +496,7 @@ func TestCoalesceTables(t *testing.T) { t.Errorf("Unexpected name: %s", dst2["name"]) } - addr2, ok := dst2["address"].(map[string]interface{}) + addr2, ok := dst2["address"].(map[string]any) if !ok { t.Fatal("Address went away.") } @@ -513,7 +513,7 @@ func TestCoalesceTables(t *testing.T) { t.Errorf("Unexpected Country: %v", addr2["country"]) } - if det2, ok := dst2["details"].(map[string]interface{}); !ok { + if det2, ok := dst2["details"].(map[string]any); !ok { t.Fatalf("Details is the wrong type: %v", dst2["details"]) } else if _, ok := det2["friends"]; !ok { t.Error("Could not find your friends. Maybe you don't have any. :-(") @@ -529,28 +529,28 @@ func TestCoalesceTables(t *testing.T) { } func TestMergeTables(t *testing.T) { - dst := map[string]interface{}{ + dst := map[string]any{ "name": "Ishmael", - "address": map[string]interface{}{ + "address": map[string]any{ "street": "123 Spouter Inn Ct.", "city": "Nantucket", "country": nil, }, - "details": map[string]interface{}{ + "details": map[string]any{ "friends": []string{"Tashtego"}, }, "boat": "pequod", "hole": nil, } - src := map[string]interface{}{ + src := map[string]any{ "occupation": "whaler", - "address": map[string]interface{}{ + "address": map[string]any{ "state": "MA", "street": "234 Spouter Inn Ct.", "country": "US", }, "details": "empty", - "boat": map[string]interface{}{ + "boat": map[string]any{ "mast": true, }, "hole": "black", @@ -567,7 +567,7 @@ func TestMergeTables(t *testing.T) { t.Errorf("Unexpected occupation: %s", dst["occupation"]) } - addr, ok := dst["address"].(map[string]interface{}) + addr, ok := dst["address"].(map[string]any) if !ok { t.Fatal("Address went away.") } @@ -590,7 +590,7 @@ func TestMergeTables(t *testing.T) { t.Error("The country is left out.") } - if det, ok := dst["details"].(map[string]interface{}); !ok { + if det, ok := dst["details"].(map[string]any); !ok { t.Fatalf("Details is the wrong type: %v", dst["details"]) } else if _, ok := det["friends"]; !ok { t.Error("Could not find your friends. Maybe you don't have any. :-(") @@ -606,14 +606,14 @@ func TestMergeTables(t *testing.T) { t.Error("The hole no longer exists.") } - dst2 := map[string]interface{}{ + dst2 := map[string]any{ "name": "Ishmael", - "address": map[string]interface{}{ + "address": map[string]any{ "street": "123 Spouter Inn Ct.", "city": "Nantucket", "country": "US", }, - "details": map[string]interface{}{ + "details": map[string]any{ "friends": []string{"Tashtego"}, }, "boat": "pequod", @@ -629,7 +629,7 @@ func TestMergeTables(t *testing.T) { t.Errorf("Unexpected name: %s", dst2["name"]) } - addr2, ok := dst2["address"].(map[string]interface{}) + addr2, ok := dst2["address"].(map[string]any) if !ok { t.Fatal("Address went away.") } @@ -646,7 +646,7 @@ func TestMergeTables(t *testing.T) { t.Errorf("Unexpected Country: %v", addr2["country"]) } - if det2, ok := dst2["details"].(map[string]interface{}); !ok { + if det2, ok := dst2["details"].(map[string]any); !ok { t.Fatalf("Details is the wrong type: %v", dst2["details"]) } else if _, ok := det2["friends"]; !ok { t.Error("Could not find your friends. Maybe you don't have any. :-(") @@ -669,24 +669,24 @@ func TestCoalesceValuesWarnings(t *testing.T) { c := withDeps(&chart.Chart{ Metadata: &chart.Metadata{Name: "level1"}, - Values: map[string]interface{}{ + Values: map[string]any{ "name": "moby", }, }, withDeps(&chart.Chart{ Metadata: &chart.Metadata{Name: "level2"}, - Values: map[string]interface{}{ + Values: map[string]any{ "name": "pequod", }, }, &chart.Chart{ Metadata: &chart.Metadata{Name: "level3"}, - Values: map[string]interface{}{ + Values: map[string]any{ "name": "ahab", "boat": true, - "spear": map[string]interface{}{ + "spear": map[string]any{ "tip": true, - "sail": map[string]interface{}{ + "sail": map[string]any{ "cotton": true, }, }, @@ -695,12 +695,12 @@ func TestCoalesceValuesWarnings(t *testing.T) { ), ) - vals := map[string]interface{}{ - "level2": map[string]interface{}{ - "level3": map[string]interface{}{ - "boat": map[string]interface{}{"mast": true}, - "spear": map[string]interface{}{ - "tip": map[string]interface{}{ + vals := map[string]any{ + "level2": map[string]any{ + "level3": map[string]any{ + "boat": map[string]any{"mast": true}, + "spear": map[string]any{ + "tip": map[string]any{ "sharp": true, }, "sail": true, @@ -710,7 +710,7 @@ func TestCoalesceValuesWarnings(t *testing.T) { } warnings := make([]string, 0) - printf := func(format string, v ...interface{}) { + printf := func(format string, v ...any) { t.Logf(format, v...) warnings = append(warnings, fmt.Sprintf(format, v...)) } diff --git a/pkg/chart/v2/util/create.go b/pkg/chart/v2/util/create.go index bf572c707..0d7ae8d5c 100644 --- a/pkg/chart/v2/util/create.go +++ b/pkg/chart/v2/util/create.go @@ -669,7 +669,7 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error { return fmt.Errorf("reading values file: %w", err) } - var m map[string]interface{} + var m map[string]any if err := yaml.Unmarshal(transform(string(b), schart.Name()), &m); err != nil { return fmt.Errorf("transforming values file: %w", err) } diff --git a/pkg/strvals/literal_parser_test.go b/pkg/strvals/literal_parser_test.go index 6a76458f5..cc3d73d50 100644 --- a/pkg/strvals/literal_parser_test.go +++ b/pkg/strvals/literal_parser_test.go @@ -26,7 +26,7 @@ import ( func TestParseLiteral(t *testing.T) { cases := []struct { str string - expect map[string]interface{} + expect map[string]any err bool }{ { @@ -35,61 +35,61 @@ func TestParseLiteral(t *testing.T) { }, { str: "name=", - expect: map[string]interface{}{"name": ""}, + expect: map[string]any{"name": ""}, }, { str: "name=value", - expect: map[string]interface{}{"name": "value"}, + expect: map[string]any{"name": "value"}, err: false, }, { str: "long_int_string=1234567890", - expect: map[string]interface{}{"long_int_string": "1234567890"}, + expect: map[string]any{"long_int_string": "1234567890"}, err: false, }, { str: "boolean=true", - expect: map[string]interface{}{"boolean": "true"}, + expect: map[string]any{"boolean": "true"}, err: false, }, { str: "is_null=null", - expect: map[string]interface{}{"is_null": "null"}, + expect: map[string]any{"is_null": "null"}, err: false, }, { str: "zero=0", - expect: map[string]interface{}{"zero": "0"}, + expect: map[string]any{"zero": "0"}, err: false, }, { str: "name1=null,name2=value2", - expect: map[string]interface{}{"name1": "null,name2=value2"}, + expect: map[string]any{"name1": "null,name2=value2"}, err: false, }, { str: "name1=value,,,tail", - expect: map[string]interface{}{"name1": "value,,,tail"}, + expect: map[string]any{"name1": "value,,,tail"}, err: false, }, { str: "leading_zeros=00009", - expect: map[string]interface{}{"leading_zeros": "00009"}, + expect: map[string]any{"leading_zeros": "00009"}, err: false, }, { str: "name=one two three", - expect: map[string]interface{}{"name": "one two three"}, + expect: map[string]any{"name": "one two three"}, err: false, }, { str: "outer.inner=value", - expect: map[string]interface{}{"outer": map[string]interface{}{"inner": "value"}}, + expect: map[string]any{"outer": map[string]any{"inner": "value"}}, err: false, }, { str: "outer.middle.inner=value", - expect: map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}}, + expect: map[string]any{"outer": map[string]any{"middle": map[string]any{"inner": "value"}}}, err: false, }, { @@ -98,7 +98,7 @@ func TestParseLiteral(t *testing.T) { }, { str: "name1.name2=", - expect: map[string]interface{}{"name1": map[string]interface{}{"name2": ""}}, + expect: map[string]any{"name1": map[string]any{"name2": ""}}, err: false, }, { @@ -111,20 +111,20 @@ func TestParseLiteral(t *testing.T) { }, { str: "name1={value1,value2}", - expect: map[string]interface{}{"name1": "{value1,value2}"}, + expect: map[string]any{"name1": "{value1,value2}"}, }, // List support { str: "list[0]=foo", - expect: map[string]interface{}{"list": []string{"foo"}}, + expect: map[string]any{"list": []string{"foo"}}, err: false, }, { str: "list[0].foo=bar", - expect: map[string]interface{}{ - "list": []interface{}{ - map[string]interface{}{"foo": "bar"}, + expect: map[string]any{ + "list": []any{ + map[string]any{"foo": "bar"}, }, }, err: false, @@ -135,7 +135,7 @@ func TestParseLiteral(t *testing.T) { }, { str: "list[3]=bar", - expect: map[string]interface{}{"list": []interface{}{nil, nil, nil, "bar"}}, + expect: map[string]any{"list": []any{nil, nil, nil, "bar"}}, err: false, }, { @@ -144,133 +144,133 @@ func TestParseLiteral(t *testing.T) { }, { str: "noval[0]", - expect: map[string]interface{}{"noval": []interface{}{}}, + expect: map[string]any{"noval": []any{}}, err: false, }, { str: "noval[0]=", - expect: map[string]interface{}{"noval": []interface{}{""}}, + expect: map[string]any{"noval": []any{""}}, err: false, }, { str: "nested[0][0]=1", - expect: map[string]interface{}{"nested": []interface{}{[]interface{}{"1"}}}, + expect: map[string]any{"nested": []any{[]any{"1"}}}, err: false, }, { str: "nested[1][1]=1", - expect: map[string]interface{}{"nested": []interface{}{nil, []interface{}{nil, "1"}}}, + expect: map[string]any{"nested": []any{nil, []any{nil, "1"}}}, err: false, }, { str: "name1.name2[0].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{{"foo": "bar"}}, + expect: map[string]any{ + "name1": map[string]any{ + "name2": []map[string]any{{"foo": "bar"}}, }, }, }, { str: "name1.name2[1].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{nil, {"foo": "bar"}}, + expect: map[string]any{ + "name1": map[string]any{ + "name2": []map[string]any{nil, {"foo": "bar"}}, }, }, }, { str: "name1.name2[1].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{nil, {"foo": "bar"}}, + expect: map[string]any{ + "name1": map[string]any{ + "name2": []map[string]any{nil, {"foo": "bar"}}, }, }, }, { str: "]={}].", - expect: map[string]interface{}{"]": "{}]."}, + expect: map[string]any{"]": "{}]."}, err: false, }, // issue test cases: , = $ ( ) { } . \ \\ { str: "name=val,val", - expect: map[string]interface{}{"name": "val,val"}, + expect: map[string]any{"name": "val,val"}, err: false, }, { str: "name=val.val", - expect: map[string]interface{}{"name": "val.val"}, + expect: map[string]any{"name": "val.val"}, err: false, }, { str: "name=val=val", - expect: map[string]interface{}{"name": "val=val"}, + expect: map[string]any{"name": "val=val"}, err: false, }, { str: "name=val$val", - expect: map[string]interface{}{"name": "val$val"}, + expect: map[string]any{"name": "val$val"}, err: false, }, { str: "name=(value", - expect: map[string]interface{}{"name": "(value"}, + expect: map[string]any{"name": "(value"}, err: false, }, { str: "name=value)", - expect: map[string]interface{}{"name": "value)"}, + expect: map[string]any{"name": "value)"}, err: false, }, { str: "name=(value)", - expect: map[string]interface{}{"name": "(value)"}, + expect: map[string]any{"name": "(value)"}, err: false, }, { str: "name={value", - expect: map[string]interface{}{"name": "{value"}, + expect: map[string]any{"name": "{value"}, err: false, }, { str: "name=value}", - expect: map[string]interface{}{"name": "value}"}, + expect: map[string]any{"name": "value}"}, err: false, }, { str: "name={value}", - expect: map[string]interface{}{"name": "{value}"}, + expect: map[string]any{"name": "{value}"}, err: false, }, { str: "name={value1,value2}", - expect: map[string]interface{}{"name": "{value1,value2}"}, + expect: map[string]any{"name": "{value1,value2}"}, err: false, }, { str: `name=val\val`, - expect: map[string]interface{}{"name": `val\val`}, + expect: map[string]any{"name": `val\val`}, err: false, }, { str: `name=val\\val`, - expect: map[string]interface{}{"name": `val\\val`}, + expect: map[string]any{"name": `val\\val`}, err: false, }, { str: `name=val\\\val`, - expect: map[string]interface{}{"name": `val\\\val`}, + expect: map[string]any{"name": `val\\\val`}, err: false, }, { str: `name={val,.?*v\0a!l)some`, - expect: map[string]interface{}{"name": `{val,.?*v\0a!l)some`}, + expect: map[string]any{"name": `{val,.?*v\0a!l)some`}, err: false, }, { str: `name=em%GT)tqUDqz,i-\h+Mbqs-!:.m\\rE=mkbM#rR}@{-k@`, - expect: map[string]interface{}{"name": `em%GT)tqUDqz,i-\h+Mbqs-!:.m\\rE=mkbM#rR}@{-k@`}, + expect: map[string]any{"name": `em%GT)tqUDqz,i-\h+Mbqs-!:.m\\rE=mkbM#rR}@{-k@`}, }, } @@ -307,20 +307,20 @@ func TestParseLiteralInto(t *testing.T) { tests := []struct { input string input2 string - got map[string]interface{} - expect map[string]interface{} + got map[string]any + expect map[string]any err bool }{ { input: "outer.inner1=value1,outer.inner3=value3,outer.inner4=4", - got: map[string]interface{}{ - "outer": map[string]interface{}{ + got: map[string]any{ + "outer": map[string]any{ "inner1": "overwrite", "inner2": "value2", }, }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ + expect: map[string]any{ + "outer": map[string]any{ "inner1": "value1,outer.inner3=value3,outer.inner4=4", "inner2": "value2", }}, @@ -329,9 +329,9 @@ func TestParseLiteralInto(t *testing.T) { { input: "listOuter[0][0].type=listValue", input2: "listOuter[0][0].status=alive", - got: map[string]interface{}{}, - expect: map[string]interface{}{ - "listOuter": [][]interface{}{{map[string]string{ + got: map[string]any{}, + expect: map[string]any{ + "listOuter": [][]any{{map[string]string{ "type": "listValue", "status": "alive", }}}, @@ -341,9 +341,9 @@ func TestParseLiteralInto(t *testing.T) { { input: "listOuter[0][0].type=listValue", input2: "listOuter[1][0].status=alive", - got: map[string]interface{}{}, - expect: map[string]interface{}{ - "listOuter": [][]interface{}{ + got: map[string]any{}, + expect: map[string]any{ + "listOuter": [][]any{ { map[string]string{"type": "listValue"}, }, @@ -357,17 +357,17 @@ func TestParseLiteralInto(t *testing.T) { { input: "listOuter[0][1][0].type=listValue", input2: "listOuter[0][0][1].status=alive", - got: map[string]interface{}{ - "listOuter": []interface{}{ - []interface{}{ - []interface{}{ + got: map[string]any{ + "listOuter": []any{ + []any{ + []any{ map[string]string{"exited": "old"}, }, }, }, }, - expect: map[string]interface{}{ - "listOuter": [][][]interface{}{ + expect: map[string]any{ + "listOuter": [][][]any{ { { map[string]string{"exited": "old"}, @@ -429,13 +429,13 @@ func TestParseLiteralNestedLevels(t *testing.T) { tests := []struct { str string - expect map[string]interface{} + expect map[string]any err bool errStr string }{ { "outer.middle.inner=value", - map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}}, + map[string]any{"outer": map[string]any{"middle": map[string]any{"inner": "value"}}}, false, "", }, diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go index 73403fc52..e3f8b9d8e 100644 --- a/pkg/strvals/parser_test.go +++ b/pkg/strvals/parser_test.go @@ -26,48 +26,48 @@ import ( func TestSetIndex(t *testing.T) { tests := []struct { name string - initial []interface{} - expect []interface{} + initial []any + expect []any add int val int err bool }{ { name: "short", - initial: []interface{}{0, 1}, - expect: []interface{}{0, 1, 2}, + initial: []any{0, 1}, + expect: []any{0, 1, 2}, add: 2, val: 2, err: false, }, { name: "equal", - initial: []interface{}{0, 1}, - expect: []interface{}{0, 2}, + initial: []any{0, 1}, + expect: []any{0, 2}, add: 1, val: 2, err: false, }, { name: "long", - initial: []interface{}{0, 1, 2, 3, 4, 5}, - expect: []interface{}{0, 1, 2, 4, 4, 5}, + initial: []any{0, 1, 2, 3, 4, 5}, + expect: []any{0, 1, 2, 4, 4, 5}, add: 3, val: 4, err: false, }, { name: "negative", - initial: []interface{}{0, 1, 2, 3, 4, 5}, - expect: []interface{}{0, 1, 2, 3, 4, 5}, + initial: []any{0, 1, 2, 3, 4, 5}, + expect: []any{0, 1, 2, 3, 4, 5}, add: -1, val: 4, err: true, }, { name: "large", - initial: []interface{}{0, 1, 2, 3, 4, 5}, - expect: []interface{}{0, 1, 2, 3, 4, 5}, + initial: []any{0, 1, 2, 3, 4, 5}, + expect: []any{0, 1, 2, 3, 4, 5}, add: MaxIndex + 1, val: 4, err: true, @@ -104,53 +104,53 @@ func TestSetIndex(t *testing.T) { func TestParseSet(t *testing.T) { testsString := []struct { str string - expect map[string]interface{} + expect map[string]any err bool }{ { str: "long_int_string=1234567890", - expect: map[string]interface{}{"long_int_string": "1234567890"}, + expect: map[string]any{"long_int_string": "1234567890"}, err: false, }, { str: "boolean=true", - expect: map[string]interface{}{"boolean": "true"}, + expect: map[string]any{"boolean": "true"}, err: false, }, { str: "is_null=null", - expect: map[string]interface{}{"is_null": "null"}, + expect: map[string]any{"is_null": "null"}, err: false, }, { str: "zero=0", - expect: map[string]interface{}{"zero": "0"}, + expect: map[string]any{"zero": "0"}, err: false, }, } tests := []struct { str string - expect map[string]interface{} + expect map[string]any err bool }{ { "name1=null,f=false,t=true", - map[string]interface{}{"name1": nil, "f": false, "t": true}, + map[string]any{"name1": nil, "f": false, "t": true}, false, }, { "name1=value1", - map[string]interface{}{"name1": "value1"}, + map[string]any{"name1": "value1"}, false, }, { "name1=value1,name2=value2", - map[string]interface{}{"name1": "value1", "name2": "value2"}, + map[string]any{"name1": "value1", "name2": "value2"}, false, }, { "name1=value1,name2=value2,", - map[string]interface{}{"name1": "value1", "name2": "value2"}, + map[string]any{"name1": "value1", "name2": "value2"}, false, }, { @@ -159,27 +159,27 @@ func TestParseSet(t *testing.T) { }, { str: "name1=,name2=value2", - expect: map[string]interface{}{"name1": "", "name2": "value2"}, + expect: map[string]any{"name1": "", "name2": "value2"}, }, { str: "leading_zeros=00009", - expect: map[string]interface{}{"leading_zeros": "00009"}, + expect: map[string]any{"leading_zeros": "00009"}, }, { str: "zero_int=0", - expect: map[string]interface{}{"zero_int": 0}, + expect: map[string]any{"zero_int": 0}, }, { str: "long_int=1234567890", - expect: map[string]interface{}{"long_int": 1234567890}, + expect: map[string]any{"long_int": 1234567890}, }, { str: "boolean=true", - expect: map[string]interface{}{"boolean": true}, + expect: map[string]any{"boolean": true}, }, { str: "is_null=null", - expect: map[string]interface{}{"is_null": nil}, + expect: map[string]any{"is_null": nil}, err: false, }, { @@ -200,40 +200,40 @@ func TestParseSet(t *testing.T) { }, { "name1=one\\,two,name2=three\\,four", - map[string]interface{}{"name1": "one,two", "name2": "three,four"}, + map[string]any{"name1": "one,two", "name2": "three,four"}, false, }, { "name1=one\\=two,name2=three\\=four", - map[string]interface{}{"name1": "one=two", "name2": "three=four"}, + map[string]any{"name1": "one=two", "name2": "three=four"}, false, }, { "name1=one two three,name2=three two one", - map[string]interface{}{"name1": "one two three", "name2": "three two one"}, + map[string]any{"name1": "one two three", "name2": "three two one"}, false, }, { "outer.inner=value", - map[string]interface{}{"outer": map[string]interface{}{"inner": "value"}}, + map[string]any{"outer": map[string]any{"inner": "value"}}, false, }, { "outer.middle.inner=value", - map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}}, + map[string]any{"outer": map[string]any{"middle": map[string]any{"inner": "value"}}}, false, }, { "outer.inner1=value,outer.inner2=value2", - map[string]interface{}{"outer": map[string]interface{}{"inner1": "value", "inner2": "value2"}}, + map[string]any{"outer": map[string]any{"inner1": "value", "inner2": "value2"}}, false, }, { "outer.inner1=value,outer.middle.inner=value", - map[string]interface{}{ - "outer": map[string]interface{}{ + map[string]any{ + "outer": map[string]any{ "inner1": "value", - "middle": map[string]interface{}{ + "middle": map[string]any{ "inner": "value", }, }, @@ -250,7 +250,7 @@ func TestParseSet(t *testing.T) { }, { str: "name1.name2=", - expect: map[string]interface{}{"name1": map[string]interface{}{"name2": ""}}, + expect: map[string]any{"name1": map[string]any{"name2": ""}}, }, { str: "name1.=name2", @@ -262,12 +262,12 @@ func TestParseSet(t *testing.T) { }, { "name1={value1,value2}", - map[string]interface{}{"name1": []string{"value1", "value2"}}, + map[string]any{"name1": []string{"value1", "value2"}}, false, }, { "name1={value1,value2},name2={value1,value2}", - map[string]interface{}{ + map[string]any{ "name1": []string{"value1", "value2"}, "name2": []string{"value1", "value2"}, }, @@ -275,12 +275,12 @@ func TestParseSet(t *testing.T) { }, { "name1={1021,902}", - map[string]interface{}{"name1": []int{1021, 902}}, + map[string]any{"name1": []int{1021, 902}}, false, }, { "name1.name2={value1,value2}", - map[string]interface{}{"name1": map[string]interface{}{"name2": []string{"value1", "value2"}}}, + map[string]any{"name1": map[string]any{"name2": []string{"value1", "value2"}}}, false, }, { @@ -290,21 +290,21 @@ func TestParseSet(t *testing.T) { // List support { str: "list[0]=foo", - expect: map[string]interface{}{"list": []string{"foo"}}, + expect: map[string]any{"list": []string{"foo"}}, }, { str: "list[0].foo=bar", - expect: map[string]interface{}{ - "list": []interface{}{ - map[string]interface{}{"foo": "bar"}, + expect: map[string]any{ + "list": []any{ + map[string]any{"foo": "bar"}, }, }, }, { str: "list[0].foo=bar,list[0].hello=world", - expect: map[string]interface{}{ - "list": []interface{}{ - map[string]interface{}{"foo": "bar", "hello": "world"}, + expect: map[string]any{ + "list": []any{ + map[string]any{"foo": "bar", "hello": "world"}, }, }, }, @@ -314,15 +314,15 @@ func TestParseSet(t *testing.T) { }, { str: "list[0]=foo,list[1]=bar", - expect: map[string]interface{}{"list": []string{"foo", "bar"}}, + expect: map[string]any{"list": []string{"foo", "bar"}}, }, { str: "list[0]=foo,list[1]=bar,", - expect: map[string]interface{}{"list": []string{"foo", "bar"}}, + expect: map[string]any{"list": []string{"foo", "bar"}}, }, { str: "list[0]=foo,list[3]=bar", - expect: map[string]interface{}{"list": []interface{}{"foo", nil, nil, "bar"}}, + expect: map[string]any{"list": []any{"foo", nil, nil, "bar"}}, }, { str: "list[0]=foo,list[-20]=bar", @@ -334,41 +334,41 @@ func TestParseSet(t *testing.T) { }, { str: "noval[0]", - expect: map[string]interface{}{"noval": []interface{}{}}, + expect: map[string]any{"noval": []any{}}, }, { str: "noval[0]=", - expect: map[string]interface{}{"noval": []interface{}{""}}, + expect: map[string]any{"noval": []any{""}}, }, { str: "nested[0][0]=1", - expect: map[string]interface{}{"nested": []interface{}{[]interface{}{1}}}, + expect: map[string]any{"nested": []any{[]any{1}}}, }, { str: "nested[1][1]=1", - expect: map[string]interface{}{"nested": []interface{}{nil, []interface{}{nil, 1}}}, + expect: map[string]any{"nested": []any{nil, []any{nil, 1}}}, }, { str: "name1.name2[0].foo=bar,name1.name2[1].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{{"foo": "bar"}, {"foo": "bar"}}, + expect: map[string]any{ + "name1": map[string]any{ + "name2": []map[string]any{{"foo": "bar"}, {"foo": "bar"}}, }, }, }, { str: "name1.name2[1].foo=bar,name1.name2[0].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{{"foo": "bar"}, {"foo": "bar"}}, + expect: map[string]any{ + "name1": map[string]any{ + "name2": []map[string]any{{"foo": "bar"}, {"foo": "bar"}}, }, }, }, { str: "name1.name2[1].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{nil, {"foo": "bar"}}, + expect: map[string]any{ + "name1": map[string]any{ + "name2": []map[string]any{nil, {"foo": "bar"}}, }, }, }, @@ -434,20 +434,20 @@ func TestParseInto(t *testing.T) { tests := []struct { input string input2 string - got map[string]interface{} - expect map[string]interface{} + got map[string]any + expect map[string]any err bool }{ { input: "outer.inner1=value1,outer.inner3=value3,outer.inner4=4", - got: map[string]interface{}{ - "outer": map[string]interface{}{ + got: map[string]any{ + "outer": map[string]any{ "inner1": "overwrite", "inner2": "value2", }, }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ + expect: map[string]any{ + "outer": map[string]any{ "inner1": "value1", "inner2": "value2", "inner3": "value3", @@ -458,9 +458,9 @@ func TestParseInto(t *testing.T) { { input: "listOuter[0][0].type=listValue", input2: "listOuter[0][0].status=alive", - got: map[string]interface{}{}, - expect: map[string]interface{}{ - "listOuter": [][]interface{}{{map[string]string{ + got: map[string]any{}, + expect: map[string]any{ + "listOuter": [][]any{{map[string]string{ "type": "listValue", "status": "alive", }}}, @@ -470,9 +470,9 @@ func TestParseInto(t *testing.T) { { input: "listOuter[0][0].type=listValue", input2: "listOuter[1][0].status=alive", - got: map[string]interface{}{}, - expect: map[string]interface{}{ - "listOuter": [][]interface{}{ + got: map[string]any{}, + expect: map[string]any{ + "listOuter": [][]any{ { map[string]string{"type": "listValue"}, }, @@ -486,17 +486,17 @@ func TestParseInto(t *testing.T) { { input: "listOuter[0][1][0].type=listValue", input2: "listOuter[0][0][1].status=alive", - got: map[string]interface{}{ - "listOuter": []interface{}{ - []interface{}{ - []interface{}{ + got: map[string]any{ + "listOuter": []any{ + []any{ + []any{ map[string]string{"exited": "old"}, }, }, }, }, - expect: map[string]interface{}{ - "listOuter": [][][]interface{}{ + expect: map[string]any{ + "listOuter": [][][]any{ { { map[string]string{"exited": "old"}, @@ -544,15 +544,15 @@ func TestParseInto(t *testing.T) { } func TestParseIntoString(t *testing.T) { - got := map[string]interface{}{ - "outer": map[string]interface{}{ + got := map[string]any{ + "outer": map[string]any{ "inner1": "overwrite", "inner2": "value2", }, } input := "outer.inner1=1,outer.inner3=3" - expect := map[string]interface{}{ - "outer": map[string]interface{}{ + expect := map[string]any{ + "outer": map[string]any{ "inner1": "1", "inner2": "value2", "inner3": "3", @@ -580,20 +580,20 @@ func TestParseIntoString(t *testing.T) { func TestParseJSON(t *testing.T) { tests := []struct { input string - got map[string]interface{} - expect map[string]interface{} + got map[string]any + expect map[string]any err bool }{ { // set json scalars values, and replace one existing key input: "outer.inner1=\"1\",outer.inner3=3,outer.inner4=true,outer.inner5=\"true\"", - got: map[string]interface{}{ - "outer": map[string]interface{}{ + got: map[string]any{ + "outer": map[string]any{ "inner1": "overwrite", "inner2": "value2", }, }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ + expect: map[string]any{ + "outer": map[string]any{ "inner1": "1", "inner2": "value2", "inner3": 3, @@ -605,43 +605,43 @@ func TestParseJSON(t *testing.T) { }, { // set json objects and arrays, and replace one existing key input: "outer.inner1={\"a\":\"1\",\"b\":2,\"c\":[1,2,3]},outer.inner3=[\"new value 1\",\"new value 2\"],outer.inner4={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner5=[{\"A\":\"1\",\"B\":2,\"C\":[1,2,3]}]", - got: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": map[string]interface{}{ + got: map[string]any{ + "outer": map[string]any{ + "inner1": map[string]any{ "x": "overwrite", }, "inner2": "value2", - "inner3": []interface{}{ + "inner3": []any{ "overwrite", }, }, }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": map[string]interface{}{"a": "1", "b": 2, "c": []interface{}{1, 2, 3}}, + expect: map[string]any{ + "outer": map[string]any{ + "inner1": map[string]any{"a": "1", "b": 2, "c": []any{1, 2, 3}}, "inner2": "value2", - "inner3": []interface{}{"new value 1", "new value 2"}, - "inner4": map[string]interface{}{"aa": "1", "bb": 2, "cc": []interface{}{1, 2, 3}}, - "inner5": []interface{}{map[string]interface{}{"A": "1", "B": 2, "C": []interface{}{1, 2, 3}}}, + "inner3": []any{"new value 1", "new value 2"}, + "inner4": map[string]any{"aa": "1", "bb": 2, "cc": []any{1, 2, 3}}, + "inner5": []any{map[string]any{"A": "1", "B": 2, "C": []any{1, 2, 3}}}, }, }, err: false, }, { // null assignment, and no value assigned (equivalent to null) input: "outer.inner1=,outer.inner3={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner3.cc[1]=null", - got: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": map[string]interface{}{ + got: map[string]any{ + "outer": map[string]any{ + "inner1": map[string]any{ "x": "overwrite", }, "inner2": "value2", }, }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ + expect: map[string]any{ + "outer": map[string]any{ "inner1": nil, "inner2": "value2", - "inner3": map[string]interface{}{"aa": "1", "bb": 2, "cc": []interface{}{1, nil, 3}}, + "inner3": map[string]any{"aa": "1", "bb": 2, "cc": []any{1, nil, 3}}, }, }, err: false, @@ -680,10 +680,10 @@ func TestParseJSON(t *testing.T) { func TestParseFile(t *testing.T) { input := "name1=path1" - expect := map[string]interface{}{ + expect := map[string]any{ "name1": "value1", } - rs2v := func(rs []rune) (interface{}, error) { + rs2v := func(rs []rune) (any, error) { v := string(rs) if v != "path1" { t.Errorf("%s: runesToVal: Expected value path1, got %s", input, v) @@ -712,12 +712,12 @@ func TestParseFile(t *testing.T) { } func TestParseIntoFile(t *testing.T) { - got := map[string]interface{}{} + got := map[string]any{} input := "name1=path1" - expect := map[string]interface{}{ + expect := map[string]any{ "name1": "value1", } - rs2v := func(rs []rune) (interface{}, error) { + rs2v := func(rs []rune) (any, error) { v := string(rs) if v != "path1" { t.Errorf("%s: runesToVal: Expected value path1, got %s", input, v) @@ -768,13 +768,13 @@ func TestParseSetNestedLevels(t *testing.T) { } tests := []struct { str string - expect map[string]interface{} + expect map[string]any err bool errStr string }{ { "outer.middle.inner=value", - map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}}, + map[string]any{"outer": map[string]any{"middle": map[string]any{"inner": "value"}}}, false, "", }, From 5cc2e55714d20e6d1bd2663878a00571c084d6c2 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 6 Feb 2026 16:11:01 +0100 Subject: [PATCH 164/571] chore(pkg): fix modernize linter #### Description fix modernize linter in pkg/chart/v2/lint/rules Signed-off-by: Matthieu MOREL --- pkg/action/install.go | 6 +++--- pkg/chart/common/util/values.go | 8 ++++---- pkg/chart/interfaces.go | 8 ++++---- pkg/chart/v2/lint/rules/chartfile.go | 10 +++++----- pkg/chart/v2/lint/rules/values.go | 6 +++--- pkg/cli/values/options.go | 8 ++++---- pkg/repo/v1/chartrepo_test.go | 12 ++++-------- 7 files changed, 27 insertions(+), 31 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index 0fe1f1a6e..1af46676e 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -252,7 +252,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error { // // If DryRun is set to true, this will prepare the release, but not install it -func (i *Install) Run(chrt ci.Charter, vals map[string]interface{}) (ri.Releaser, error) { +func (i *Install) Run(chrt ci.Charter, vals map[string]any) (ri.Releaser, error) { ctx := context.Background() return i.RunWithContext(ctx, chrt, vals) } @@ -261,7 +261,7 @@ func (i *Install) Run(chrt ci.Charter, vals map[string]interface{}) (ri.Releaser // // When the task is cancelled through ctx, the function returns and the install // proceeds in the background. -func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[string]interface{}) (ri.Releaser, error) { +func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[string]any) (ri.Releaser, error) { var chrt *chart.Chart switch c := ch.(type) { case *chart.Chart: @@ -638,7 +638,7 @@ func releaseV1ListToReleaserList(ls []*release.Release) ([]ri.Releaser, error) { } // createRelease creates a new release object -func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{}, labels map[string]string) *release.Release { +func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]any, labels map[string]string) *release.Release { ts := i.cfg.Now() r := &release.Release{ diff --git a/pkg/chart/common/util/values.go b/pkg/chart/common/util/values.go index 85cb29012..95ac7ba4d 100644 --- a/pkg/chart/common/util/values.go +++ b/pkg/chart/common/util/values.go @@ -26,14 +26,14 @@ import ( // ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files // // This takes both ReleaseOptions and Capabilities to merge into the render values. -func ToRenderValues(chrt chart.Charter, chrtVals map[string]interface{}, options common.ReleaseOptions, caps *common.Capabilities) (common.Values, error) { +func ToRenderValues(chrt chart.Charter, chrtVals map[string]any, options common.ReleaseOptions, caps *common.Capabilities) (common.Values, error) { return ToRenderValuesWithSchemaValidation(chrt, chrtVals, options, caps, false) } // ToRenderValuesWithSchemaValidation composes the struct from the data coming from the Releases, Charts and Values files // // This takes both ReleaseOptions and Capabilities to merge into the render values. -func ToRenderValuesWithSchemaValidation(chrt chart.Charter, chrtVals map[string]interface{}, options common.ReleaseOptions, caps *common.Capabilities, skipSchemaValidation bool) (common.Values, error) { +func ToRenderValuesWithSchemaValidation(chrt chart.Charter, chrtVals map[string]any, options common.ReleaseOptions, caps *common.Capabilities, skipSchemaValidation bool) (common.Values, error) { if caps == nil { caps = common.DefaultCapabilities } @@ -41,10 +41,10 @@ func ToRenderValuesWithSchemaValidation(chrt chart.Charter, chrtVals map[string] if err != nil { return nil, err } - top := map[string]interface{}{ + top := map[string]any{ "Chart": accessor.MetadataAsMap(), "Capabilities": caps, - "Release": map[string]interface{}{ + "Release": map[string]any{ "Name": options.Name, "Namespace": options.Namespace, "IsUpgrade": options.IsUpgrade, diff --git a/pkg/chart/interfaces.go b/pkg/chart/interfaces.go index 4001bc548..6d94ad3ea 100644 --- a/pkg/chart/interfaces.go +++ b/pkg/chart/interfaces.go @@ -19,21 +19,21 @@ import ( common "helm.sh/helm/v4/pkg/chart/common" ) -type Charter interface{} +type Charter any -type Dependency interface{} +type Dependency any type Accessor interface { Name() string IsRoot() bool - MetadataAsMap() map[string]interface{} + MetadataAsMap() map[string]any Files() []*common.File Templates() []*common.File ChartFullPath() string IsLibraryChart() bool Dependencies() []Charter MetaDependencies() []Dependency - Values() map[string]interface{} + Values() map[string]any Schema() []byte Deprecated() bool } diff --git a/pkg/chart/v2/lint/rules/chartfile.go b/pkg/chart/v2/lint/rules/chartfile.go index 806363477..b2d146a27 100644 --- a/pkg/chart/v2/lint/rules/chartfile.go +++ b/pkg/chart/v2/lint/rules/chartfile.go @@ -70,15 +70,15 @@ func Chartfile(linter *support.Linter) { linter.RunLinterRule(support.WarningSev, chartFileName, validateChartVersionStrictSemVerV2(chartFile)) } -func validateChartVersionType(data map[string]interface{}) error { +func validateChartVersionType(data map[string]any) error { return isStringValue(data, "version") } -func validateChartAppVersionType(data map[string]interface{}) error { +func validateChartAppVersionType(data map[string]any) error { return isStringValue(data, "appVersion") } -func isStringValue(data map[string]interface{}, key string) error { +func isStringValue(data map[string]any, key string) error { value, ok := data[key] if !ok { return nil @@ -225,12 +225,12 @@ func validateChartType(cf *chart.Metadata) error { // loadChartFileForTypeCheck loads the Chart.yaml // in a generic form of a map[string]interface{}, so that the type // of the values can be checked -func loadChartFileForTypeCheck(filename string) (map[string]interface{}, error) { +func loadChartFileForTypeCheck(filename string) (map[string]any, error) { b, err := os.ReadFile(filename) if err != nil { return nil, err } - y := make(map[string]interface{}) + y := make(map[string]any) err = yaml.Unmarshal(b, &y) return y, err } diff --git a/pkg/chart/v2/lint/rules/values.go b/pkg/chart/v2/lint/rules/values.go index 994a6a463..8fe849c7a 100644 --- a/pkg/chart/v2/lint/rules/values.go +++ b/pkg/chart/v2/lint/rules/values.go @@ -32,7 +32,7 @@ import ( // they are only tested for well-formedness. // // If additional values are supplied, they are coalesced into the values in values.yaml. -func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]interface{}, skipSchemaValidation bool) { +func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]any, skipSchemaValidation bool) { file := "values.yaml" vf := filepath.Join(linter.ChartDir, file) fileExists := linter.RunLinterRule(support.InfoSev, file, validateValuesFileExistence(vf)) @@ -52,7 +52,7 @@ func validateValuesFileExistence(valuesPath string) error { return nil } -func validateValuesFile(valuesPath string, overrides map[string]interface{}, skipSchemaValidation bool) error { +func validateValuesFile(valuesPath string, overrides map[string]any, skipSchemaValidation bool) error { values, err := common.ReadValuesFile(valuesPath) if err != nil { return fmt.Errorf("unable to parse YAML: %w", err) @@ -63,7 +63,7 @@ func validateValuesFile(valuesPath string, overrides map[string]interface{}, ski // We could change that. For now, though, we retain that strategy, and thus can // coalesce tables (like reuse-values does) instead of doing the full chart // CoalesceValues - coalescedValues := util.CoalesceTables(make(map[string]interface{}, len(overrides)), overrides) + coalescedValues := util.CoalesceTables(make(map[string]any, len(overrides)), overrides) coalescedValues = util.CoalesceTables(coalescedValues, values) ext := filepath.Ext(valuesPath) diff --git a/pkg/cli/values/options.go b/pkg/cli/values/options.go index cd65fa885..336dfb72b 100644 --- a/pkg/cli/values/options.go +++ b/pkg/cli/values/options.go @@ -42,8 +42,8 @@ type Options struct { // MergeValues merges values from files specified via -f/--values and directly // via --set-json, --set, --set-string, or --set-file, marshaling them to YAML -func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, error) { - base := map[string]interface{}{} +func (opts *Options) MergeValues(p getter.Providers) (map[string]any, error) { + base := map[string]any{} // User specified a values files via -f/--values for _, filePath := range opts.ValueFiles { @@ -64,7 +64,7 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er trimmedValue := strings.TrimSpace(value) if len(trimmedValue) > 0 && trimmedValue[0] == '{' { // If value is JSON object format, parse it as map - var jsonMap map[string]interface{} + var jsonMap map[string]any if err := json.Unmarshal([]byte(trimmedValue), &jsonMap); err != nil { return nil, fmt.Errorf("failed parsing --set-json data JSON: %s", value) } @@ -93,7 +93,7 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er // User specified a value via --set-file for _, value := range opts.FileValues { - reader := func(rs []rune) (interface{}, error) { + reader := func(rs []rune) (any, error) { bytes, err := readFile(string(rs), p) if err != nil { return nil, err diff --git a/pkg/repo/v1/chartrepo_test.go b/pkg/repo/v1/chartrepo_test.go index 353ab62d6..7cffc04b6 100644 --- a/pkg/repo/v1/chartrepo_test.go +++ b/pkg/repo/v1/chartrepo_test.go @@ -126,24 +126,20 @@ func TestConcurrencyDownloadIndex(t *testing.T) { // 2) read index.yaml via LoadIndexFile (read operation). // This checks for race conditions and ensures correct behavior under concurrent read/write access. for range 150 { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { idx, err := repo.DownloadIndexFile() if err != nil { t.Errorf("Failed to download index file to %s: %v", idx, err) } - }() + }) - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { _, err := LoadIndexFile(indexFName) if err != nil { t.Errorf("Failed to load index file: %v", err) } - }() + }) } wg.Wait() } From 859292e31bd4ceb170050eaa49e727bcd69572e2 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 6 Feb 2026 16:11:18 +0100 Subject: [PATCH 165/571] chore(internal): fix modernize linter #### Description fix modernize linter in internal/chart/v3 Signed-off-by: Matthieu MOREL --- internal/chart/v3/chart.go | 2 +- internal/chart/v3/dependency.go | 2 +- internal/chart/v3/errors.go | 2 +- internal/chart/v3/lint/lint.go | 2 +- internal/chart/v3/lint/rules/template_test.go | 4 ++-- internal/chart/v3/util/create.go | 2 +- internal/test/test.go | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/chart/v3/chart.go b/internal/chart/v3/chart.go index 48f006e79..217594df9 100644 --- a/internal/chart/v3/chart.go +++ b/internal/chart/v3/chart.go @@ -45,7 +45,7 @@ type Chart struct { // Templates for this chart. Templates []*common.File `json:"templates"` // Values are default config for this chart. - Values map[string]interface{} `json:"values"` + Values map[string]any `json:"values"` // Schema is an optional JSON schema for imposing structure on Values Schema []byte `json:"schema"` // SchemaModTime the schema was last modified diff --git a/internal/chart/v3/dependency.go b/internal/chart/v3/dependency.go index 2d956b548..50ee5552e 100644 --- a/internal/chart/v3/dependency.go +++ b/internal/chart/v3/dependency.go @@ -44,7 +44,7 @@ type Dependency struct { Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` // ImportValues holds the mapping of source values to parent key to be imported. Each item can be a // string or pair of child/parent sublist items. - ImportValues []interface{} `json:"import-values,omitempty" yaml:"import-values,omitempty"` + ImportValues []any `json:"import-values,omitempty" yaml:"import-values,omitempty"` // Alias usable alias to be used for the chart Alias string `json:"alias,omitempty" yaml:"alias,omitempty"` } diff --git a/internal/chart/v3/errors.go b/internal/chart/v3/errors.go index 059e43f07..156dce1de 100644 --- a/internal/chart/v3/errors.go +++ b/internal/chart/v3/errors.go @@ -25,6 +25,6 @@ func (v ValidationError) Error() string { } // ValidationErrorf takes a message and formatting options and creates a ValidationError -func ValidationErrorf(msg string, args ...interface{}) ValidationError { +func ValidationErrorf(msg string, args ...any) ValidationError { return ValidationError(fmt.Sprintf(msg, args...)) } diff --git a/internal/chart/v3/lint/lint.go b/internal/chart/v3/lint/lint.go index 0cd949065..e98edfabe 100644 --- a/internal/chart/v3/lint/lint.go +++ b/internal/chart/v3/lint/lint.go @@ -43,7 +43,7 @@ func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption { } } -func RunAll(baseDir string, values map[string]interface{}, namespace string, options ...LinterOption) support.Linter { +func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter { chartDir, _ := filepath.Abs(baseDir) diff --git a/internal/chart/v3/lint/rules/template_test.go b/internal/chart/v3/lint/rules/template_test.go index 0ffc92002..b1371659f 100644 --- a/internal/chart/v3/lint/rules/template_test.go +++ b/internal/chart/v3/lint/rules/template_test.go @@ -49,7 +49,7 @@ func TestValidateAllowedExtension(t *testing.T) { } } -var values = map[string]interface{}{"nameOverride": "", "httpPort": 80} +var values = map[string]any{"nameOverride": "", "httpPort": 80} const namespace = "testNamespace" const strict = false @@ -249,7 +249,7 @@ func TestStrictTemplateParsingMapError(t *testing.T) { APIVersion: "v2", Version: "0.1.0", }, - Values: map[string]interface{}{ + Values: map[string]any{ "mymap": map[string]string{ "key1": "val1", }, diff --git a/internal/chart/v3/util/create.go b/internal/chart/v3/util/create.go index 0dfa30995..48d2120e5 100644 --- a/internal/chart/v3/util/create.go +++ b/internal/chart/v3/util/create.go @@ -670,7 +670,7 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error { return fmt.Errorf("reading values file: %w", err) } - var m map[string]interface{} + var m map[string]any if err := yaml.Unmarshal(transform(string(b), schart.Name()), &m); err != nil { return fmt.Errorf("transforming values file: %w", err) } diff --git a/internal/test/test.go b/internal/test/test.go index 632bc72fd..202e015ab 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -29,8 +29,8 @@ var updateGolden = flag.Bool("update", false, "update golden files") // TestingT describes a testing object compatible with the critical functions from the testing.T type type TestingT interface { - Fatal(...interface{}) - Fatalf(string, ...interface{}) + Fatal(...any) + Fatalf(string, ...any) HelperT } From 0d75d8611d3daa6b820d94fc95347a069b062f72 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 6 Feb 2026 16:11:24 +0100 Subject: [PATCH 166/571] chore(pkg): fix modernize linter #### Description fix modernize linter in pkg/engine Signed-off-by: Matthieu MOREL --- pkg/action/install_test.go | 60 +++++++++++++-------------- pkg/action/upgrade_test.go | 56 +++++++++++++------------- pkg/engine/engine_test.go | 62 ++++++++++++++-------------- pkg/engine/funcs.go | 46 ++++++++++----------- pkg/engine/funcs_test.go | 44 ++++++++++---------- pkg/strvals/literal_parser.go | 34 ++++++++-------- pkg/strvals/parser.go | 76 +++++++++++++++++------------------ 7 files changed, 189 insertions(+), 189 deletions(-) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 47080aef8..535ae0462 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -134,7 +134,7 @@ func TestInstallRelease(t *testing.T) { req := require.New(t) instAction := installAction(t) - vals := map[string]interface{}{} + vals := map[string]any{} ctx, done := context.WithCancel(t.Context()) resi, err := instAction.RunWithContext(ctx, buildChart(), vals) if err != nil { @@ -240,13 +240,13 @@ func TestInstallReleaseWithTakeOwnership_ResourceOwnedNoFlag(t *testing.T) { func TestInstallReleaseWithValues(t *testing.T) { is := assert.New(t) instAction := installAction(t) - userVals := map[string]interface{}{ - "nestedKey": map[string]interface{}{ + userVals := map[string]any{ + "nestedKey": map[string]any{ "simpleKey": "simpleValue", }, } - expectedUserValues := map[string]interface{}{ - "nestedKey": map[string]interface{}{ + expectedUserValues := map[string]any{ + "nestedKey": map[string]any{ "simpleKey": "simpleValue", }, } @@ -280,7 +280,7 @@ func TestInstallReleaseWithValues(t *testing.T) { func TestInstallRelease_NoName(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "" - vals := map[string]interface{}{} + vals := map[string]any{} _, err := instAction.Run(buildChart(), vals) if err == nil { t.Fatal("expected failure when no name is specified") @@ -292,7 +292,7 @@ func TestInstallRelease_WithNotes(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.ReleaseName = "with-notes" - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(withNotes("note here")), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -323,7 +323,7 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.ReleaseName = "with-notes" - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -346,7 +346,7 @@ func TestInstallRelease_WithChartAndDependencyParentNotes(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.ReleaseName = "with-notes" - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -369,7 +369,7 @@ func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "with-notes" instAction.SubNotes = true - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -395,7 +395,7 @@ func TestInstallRelease_DryRunClient(t *testing.T) { instAction := installAction(t) instAction.DryRunStrategy = dryRunStrategy - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(withSampleTemplates()), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -423,7 +423,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { // First perform a normal dry-run with the secret and confirm its presence. instAction.DryRunStrategy = DryRunClient - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -438,7 +438,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { // Perform a dry-run where the secret should not be present instAction.HideSecret = true - vals = map[string]interface{}{} + vals = map[string]any{} res2i, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -454,7 +454,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { // Ensure there is an error when HideSecret True but not in a dry-run mode instAction.DryRunStrategy = DryRunNone - vals = map[string]interface{}{} + vals = map[string]any{} _, err = instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) if err == nil { t.Fatalf("Did not get expected an error when dry-run false and hide secret is true") @@ -466,7 +466,7 @@ func TestInstallRelease_DryRun_Lookup(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.DryRunStrategy = DryRunNone - vals := map[string]interface{}{} + vals := map[string]any{} mockChart := buildChart(withSampleTemplates()) mockChart.Templates = append(mockChart.Templates, &common.File{ @@ -489,7 +489,7 @@ func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.DryRunStrategy = DryRunNone - vals := map[string]interface{}{} + vals := map[string]any{} _, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals) expectedErr := `hello/templates/incorrect:1:10 executing "hello/templates/incorrect" at <.Values.bad.doh>: @@ -507,7 +507,7 @@ func TestInstallRelease_NoHooks(t *testing.T) { instAction.ReleaseName = "no-hooks" require.NoError(t, instAction.cfg.Releases.Create(releaseStub())) - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -528,7 +528,7 @@ func TestInstallRelease_FailedHooks(t *testing.T) { outBuffer := &bytes.Buffer{} failer.PrintingKubeClient = kubefake.PrintingKubeClient{Out: io.Discard, LogOutput: outBuffer} - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(), vals) is.Error(err) res, err := releaserToV1Release(resi) @@ -548,7 +548,7 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) { require.NoError(t, instAction.cfg.Releases.Create(rel)) instAction.ReleaseName = rel.Name - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(), vals) is.NoError(err) res, err := releaserToV1Release(resi) @@ -568,13 +568,13 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) { func TestInstallRelease_KubeVersion(t *testing.T) { is := assert.New(t) instAction := installAction(t) - vals := map[string]interface{}{} + vals := map[string]any{} _, err := instAction.Run(buildChart(withKube(">=0.0.0")), vals) is.NoError(err) // This should fail for a few hundred years instAction.ReleaseName = "should-fail" - vals = map[string]interface{}{} + vals = map[string]any{} _, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals) is.Error(err) is.Contains(err.Error(), "chart requires kubeVersion: >=99.0.0 which is incompatible with Kubernetes v1.20.") @@ -588,7 +588,7 @@ func TestInstallRelease_Wait(t *testing.T) { failer.WaitError = fmt.Errorf("I timed out") instAction.cfg.KubeClient = failer instAction.WaitStrategy = kube.StatusWatcherStrategy - vals := map[string]interface{}{} + vals := map[string]any{} goroutines := instAction.getGoroutineCount() @@ -609,7 +609,7 @@ func TestInstallRelease_Wait_Interrupted(t *testing.T) { failer.WaitDuration = 10 * time.Second instAction.cfg.KubeClient = failer instAction.WaitStrategy = kube.StatusWatcherStrategy - vals := map[string]interface{}{} + vals := map[string]any{} ctx, cancel := context.WithCancel(t.Context()) time.AfterFunc(time.Second, cancel) @@ -633,7 +633,7 @@ func TestInstallRelease_WaitForJobs(t *testing.T) { instAction.cfg.KubeClient = failer instAction.WaitStrategy = kube.StatusWatcherStrategy instAction.WaitForJobs = true - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(), vals) is.Error(err) @@ -656,7 +656,7 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) { // disabling hooks to avoid an early fail when // WaitForDelete is called on the pre-delete hook execution instAction.DisableHooks = true - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(), vals) is.Error(err) @@ -679,7 +679,7 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) { failer.DeleteError = fmt.Errorf("uninstall fail") instAction.cfg.KubeClient = failer instAction.RollbackOnFailure = true - vals := map[string]interface{}{} + vals := map[string]any{} _, err := instAction.Run(buildChart(), vals) is.Error(err) @@ -697,7 +697,7 @@ func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { failer.WaitDuration = 10 * time.Second instAction.cfg.KubeClient = failer instAction.RollbackOnFailure = true - vals := map[string]interface{}{} + vals := map[string]any{} ctx, cancel := context.WithCancel(t.Context()) time.AfterFunc(time.Second, cancel) @@ -793,7 +793,7 @@ func TestNameTemplate(t *testing.T) { func TestInstallReleaseOutputDir(t *testing.T) { is := assert.New(t) instAction := installAction(t) - vals := map[string]interface{}{} + vals := map[string]any{} dir := t.TempDir() @@ -825,7 +825,7 @@ func TestInstallReleaseOutputDir(t *testing.T) { func TestInstallOutputDirWithReleaseName(t *testing.T) { is := assert.New(t) instAction := installAction(t) - vals := map[string]interface{}{} + vals := map[string]any{} dir := t.TempDir() @@ -1201,7 +1201,7 @@ func TestInstallRelease_WaitOptionsPassedDownstream(t *testing.T) { // Access the underlying FailingKubeClient to check recorded options failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - vals := map[string]interface{}{} + vals := map[string]any{} _, err := instAction.Run(buildChart(), vals) is.NoError(err) diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 848e8a682..f4606a3e9 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -60,7 +60,7 @@ func TestUpgradeRelease_Success(t *testing.T) { req.NoError(upAction.cfg.Releases.Create(rel)) upAction.WaitStrategy = kube.StatusWatcherStrategy - vals := map[string]interface{}{} + vals := map[string]any{} ctx, done := context.WithCancel(t.Context()) resi, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) @@ -94,7 +94,7 @@ func TestUpgradeRelease_Wait(t *testing.T) { failer.WaitError = fmt.Errorf("I timed out") upAction.cfg.KubeClient = failer upAction.WaitStrategy = kube.StatusWatcherStrategy - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) @@ -119,7 +119,7 @@ func TestUpgradeRelease_WaitForJobs(t *testing.T) { upAction.cfg.KubeClient = failer upAction.WaitStrategy = kube.StatusWatcherStrategy upAction.WaitForJobs = true - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) @@ -145,7 +145,7 @@ func TestUpgradeRelease_CleanupOnFail(t *testing.T) { upAction.cfg.KubeClient = failer upAction.WaitStrategy = kube.StatusWatcherStrategy upAction.CleanupOnFail = true - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) @@ -173,7 +173,7 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) { failer.WatchUntilReadyError = fmt.Errorf("arming key removed") upAction.cfg.KubeClient = failer upAction.RollbackOnFailure = true - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) @@ -202,7 +202,7 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) { failer.UpdateError = fmt.Errorf("update fail") upAction.cfg.KubeClient = failer upAction.RollbackOnFailure = true - vals := map[string]interface{}{} + vals := map[string]any{} _, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) @@ -217,17 +217,17 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { t.Run("reuse values should work with values", func(t *testing.T) { upAction := upgradeAction(t) - existingValues := map[string]interface{}{ + existingValues := map[string]any{ "name": "value", "maxHeapSize": "128m", "replicas": 2, } - newValues := map[string]interface{}{ + newValues := map[string]any{ "name": "newValue", "maxHeapSize": "512m", "cpu": "12m", } - expectedValues := map[string]interface{}{ + expectedValues := map[string]any{ "name": "newValue", "maxHeapSize": "512m", "cpu": "12m", @@ -266,8 +266,8 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { t.Run("reuse values should not install disabled charts", func(t *testing.T) { upAction := upgradeAction(t) - chartDefaultValues := map[string]interface{}{ - "subchart": map[string]interface{}{ + chartDefaultValues := map[string]any{ + "subchart": map[string]any{ "enabled": true, }, } @@ -283,8 +283,8 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { withMetadataDependency(dependency), ) now := time.Now() - existingValues := map[string]interface{}{ - "subchart": map[string]interface{}{ + existingValues := map[string]any{ + "subchart": map[string]any{ "enabled": false, }, } @@ -311,7 +311,7 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { withMetadataDependency(dependency), ) // reusing values and upgrading - resi, err := upAction.Run(rel.Name, sampleChartWithSubChart, map[string]interface{}{}) + resi, err := upAction.Run(rel.Name, sampleChartWithSubChart, map[string]any{}) is.NoError(err) res, err := releaserToV1Release(resi) is.NoError(err) @@ -330,8 +330,8 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { is.Equal(common.StatusDeployed, updatedRes.Info.Status) is.Equal(0, len(updatedRes.Chart.Dependencies()), "expected 0 dependencies") - expectedValues := map[string]interface{}{ - "subchart": map[string]interface{}{ + expectedValues := map[string]any{ + "subchart": map[string]any{ "enabled": false, }, } @@ -345,20 +345,20 @@ func TestUpgradeRelease_ResetThenReuseValues(t *testing.T) { t.Run("reset then reuse values should work with values", func(t *testing.T) { upAction := upgradeAction(t) - existingValues := map[string]interface{}{ + existingValues := map[string]any{ "name": "value", "maxHeapSize": "128m", "replicas": 2, } - newValues := map[string]interface{}{ + newValues := map[string]any{ "name": "newValue", "maxHeapSize": "512m", "cpu": "12m", } - newChartValues := map[string]interface{}{ + newChartValues := map[string]any{ "memory": "256m", } - expectedValues := map[string]interface{}{ + expectedValues := map[string]any{ "name": "newValue", "maxHeapSize": "512m", "cpu": "12m", @@ -411,7 +411,7 @@ func TestUpgradeRelease_Pending(t *testing.T) { rel2.Version = 2 require.NoError(t, upAction.cfg.Releases.Create(rel2)) - vals := map[string]interface{}{} + vals := map[string]any{} _, err := upAction.Run(rel.Name, buildChart(), vals) req.Contains(err.Error(), "progress", err) @@ -431,7 +431,7 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { failer.WaitDuration = 10 * time.Second upAction.cfg.KubeClient = failer upAction.WaitStrategy = kube.StatusWatcherStrategy - vals := map[string]interface{}{} + vals := map[string]any{} ctx, cancel := context.WithCancel(t.Context()) time.AfterFunc(time.Second, cancel) @@ -460,7 +460,7 @@ func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) { failer.WaitDuration = 5 * time.Second upAction.cfg.KubeClient = failer upAction.RollbackOnFailure = true - vals := map[string]interface{}{} + vals := map[string]any{} ctx, cancel := context.WithCancel(t.Context()) time.AfterFunc(time.Second, cancel) @@ -590,7 +590,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) { req.NoError(upAction.cfg.Releases.Create(rel)) upAction.DryRunStrategy = DryRunClient - vals := map[string]interface{}{} + vals := map[string]any{} ctx, done := context.WithCancel(t.Context()) resi, err := upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) @@ -610,7 +610,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) { // Test the case for hiding the secret to ensure it is not displayed upAction.HideSecret = true - vals = map[string]interface{}{} + vals = map[string]any{} ctx, done = context.WithCancel(t.Context()) resi, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) @@ -630,7 +630,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) { // Ensure in a dry run mode when using HideSecret upAction.DryRunStrategy = DryRunNone - vals = map[string]interface{}{} + vals = map[string]any{} ctx, done = context.WithCancel(t.Context()) _, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) @@ -752,7 +752,7 @@ func TestUpgradeRun_UnreachableKubeClient(t *testing.T) { config.KubeClient = &failingKubeClient client := NewUpgrade(config) - vals := map[string]interface{}{} + vals := map[string]any{} result, err := client.Run("", buildChart(), vals) assert.Nil(t, result) @@ -795,7 +795,7 @@ func TestUpgradeRelease_WaitOptionsPassedDownstream(t *testing.T) { // Access the underlying FailingKubeClient to check recorded options failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - vals := map[string]interface{}{} + vals := map[string]any{} _, err := upAction.Run(rel.Name, buildChart(), vals) req.NoError(err) diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index c9cdf79c3..c80c65c65 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -104,14 +104,14 @@ func TestRender(t *testing.T) { {Name: "templates/test4", ModTime: modTime, Data: []byte("{{toJson .Values}}")}, {Name: "templates/test5", ModTime: modTime, Data: []byte("{{getHostByName \"helm.sh\"}}")}, }, - Values: map[string]interface{}{"outer": "DEFAULT", "inner": "DEFAULT"}, + Values: map[string]any{"outer": "DEFAULT", "inner": "DEFAULT"}, } - vals := map[string]interface{}{ - "Values": map[string]interface{}{ + vals := map[string]any{ + "Values": map[string]any{ "outer": "spouter", "inner": "inn", - "global": map[string]interface{}{ + "global": map[string]any{ "callme": "Ishmael", }, }, @@ -226,11 +226,11 @@ func TestRenderWithDNS(t *testing.T) { Templates: []*common.File{ {Name: "templates/test1", ModTime: time.Now(), Data: []byte("{{getHostByName \"helm.sh\"}}")}, }, - Values: map[string]interface{}{}, + Values: map[string]any{}, } - vals := map[string]interface{}{ - "Values": map[string]interface{}{}, + vals := map[string]any{ + "Values": map[string]any{}, } v, err := util.CoalesceValues(c, vals) @@ -277,15 +277,15 @@ var _ ClientProvider = &testClientProvider{} // makeUnstructured is a convenience function for single-line creation of Unstructured objects. func makeUnstructured(apiVersion, kind, name, namespace string) *unstructured.Unstructured { - ret := &unstructured.Unstructured{Object: map[string]interface{}{ + ret := &unstructured.Unstructured{Object: map[string]any{ "apiVersion": apiVersion, "kind": kind, - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": name, }, }} if namespace != "" { - ret.Object["metadata"].(map[string]interface{})["namespace"] = namespace + ret.Object["metadata"].(map[string]any)["namespace"] = namespace } return ret } @@ -356,7 +356,7 @@ func TestRenderWithClientProvider(t *testing.T) { Name: "moby", Version: "1.2.3", }, - Values: map[string]interface{}{}, + Values: map[string]any{}, } modTime := time.Now() @@ -368,8 +368,8 @@ func TestRenderWithClientProvider(t *testing.T) { }) } - vals := map[string]interface{}{ - "Values": map[string]interface{}{}, + vals := map[string]any{ + "Values": map[string]any{}, } v, err := util.CoalesceValues(c, vals) @@ -401,11 +401,11 @@ func TestRenderWithClientProvider_error(t *testing.T) { Templates: []*common.File{ {Name: "templates/error", ModTime: time.Now(), Data: []byte(`{{ lookup "v1" "Error" "" "" }}`)}, }, - Values: map[string]interface{}{}, + Values: map[string]any{}, } - vals := map[string]interface{}{ - "Values": map[string]interface{}{}, + vals := map[string]any{ + "Values": map[string]any{}, } v, err := util.CoalesceValues(c, vals) @@ -438,7 +438,7 @@ func TestParallelRenderInternals(t *testing.T) { tpls := map[string]renderable{ "t": { tpl: `{{.val}}`, - vals: map[string]interface{}{"val": tt}, + vals: map[string]any{"val": tt}, }, } out, err := e.render(tpls) @@ -455,7 +455,7 @@ func TestParallelRenderInternals(t *testing.T) { } func TestParseErrors(t *testing.T) { - vals := common.Values{"Values": map[string]interface{}{}} + vals := common.Values{"Values": map[string]any{}} tplsUndefinedFunction := map[string]renderable{ "undefined_function": {tpl: `{{foo}}`, vals: vals}, @@ -471,7 +471,7 @@ func TestParseErrors(t *testing.T) { } func TestExecErrors(t *testing.T) { - vals := common.Values{"Values": map[string]interface{}{}} + vals := common.Values{"Values": map[string]any{}} cases := []struct { name string tpls map[string]renderable @@ -535,7 +535,7 @@ linebreak`, } func TestFailErrors(t *testing.T) { - vals := common.Values{"Values": map[string]interface{}{}} + vals := common.Values{"Values": map[string]any{}} failtpl := `All your base are belong to us{{ fail "This is an error" }}` tplsFailed := map[string]renderable{ @@ -643,7 +643,7 @@ func TestRenderDependency(t *testing.T) { }, }) - out, err := Render(ch, map[string]interface{}{}) + out, err := Render(ch, map[string]any{}) if err != nil { t.Fatalf("failed to render chart: %s", err) } @@ -675,7 +675,7 @@ func TestRenderNestedValues(t *testing.T) { {Name: deepestpath, ModTime: modTime, Data: []byte(`And this same {{.Values.what}} that smiles {{.Values.global.when}}`)}, {Name: checkrelease, ModTime: modTime, Data: []byte(`Tomorrow will be {{default "happy" .Release.Name }}`)}, }, - Values: map[string]interface{}{"what": "milkshake", "where": "here"}, + Values: map[string]any{"what": "milkshake", "where": "here"}, } inner := &chart.Chart{ @@ -683,7 +683,7 @@ func TestRenderNestedValues(t *testing.T) { Templates: []*common.File{ {Name: innerpath, ModTime: modTime, Data: []byte(`Old {{.Values.who}} is still a-flyin'`)}, }, - Values: map[string]interface{}{"who": "Robert", "what": "glasses"}, + Values: map[string]any{"who": "Robert", "what": "glasses"}, } inner.AddDependency(deepest) @@ -693,10 +693,10 @@ func TestRenderNestedValues(t *testing.T) { {Name: outerpath, ModTime: modTime, Data: []byte(`Gather ye {{.Values.what}} while ye may`)}, {Name: subchartspath, ModTime: modTime, Data: []byte(`The glorious Lamp of {{.Subcharts.herrick.Subcharts.deepest.Values.where}}, the {{.Subcharts.herrick.Values.what}}`)}, }, - Values: map[string]interface{}{ + Values: map[string]any{ "what": "stinkweed", "who": "me", - "herrick": map[string]interface{}{ + "herrick": map[string]any{ "who": "time", "what": "Sun", }, @@ -704,15 +704,15 @@ func TestRenderNestedValues(t *testing.T) { } outer.AddDependency(inner) - injValues := map[string]interface{}{ + injValues := map[string]any{ "what": "rosebuds", - "herrick": map[string]interface{}{ - "deepest": map[string]interface{}{ + "herrick": map[string]any{ + "deepest": map[string]any{ "what": "flower", "where": "Heaven", }, }, - "global": map[string]interface{}{ + "global": map[string]any{ "when": "to-day", }, } @@ -1349,7 +1349,7 @@ NestedHelperFunctions/charts/common/templates/_helpers_2.tpl:1:49 v := common.Values{} val, _ := util.CoalesceValues(c, v) - vals := map[string]interface{}{ + vals := map[string]any{ "Values": val.AsMap(), } _, err := Render(c, vals) @@ -1383,7 +1383,7 @@ template: no template "nested_helper.name" associated with template "gotpl"` v := common.Values{} val, _ := util.CoalesceValues(c, v) - vals := map[string]interface{}{ + vals := map[string]any{ "Values": val.AsMap(), } _, err := Render(c, vals) diff --git a/pkg/engine/funcs.go b/pkg/engine/funcs.go index a97f8f104..ba842a51a 100644 --- a/pkg/engine/funcs.go +++ b/pkg/engine/funcs.go @@ -64,13 +64,13 @@ func funcMap() template.FuncMap { // This is a placeholder for the "include" function, which is // late-bound to a template. By declaring it here, we preserve the // integrity of the linter. - "include": func(string, interface{}) string { return "not implemented" }, - "tpl": func(string, interface{}) interface{} { return "not implemented" }, - "required": func(string, interface{}) (interface{}, error) { return "not implemented", nil }, + "include": func(string, any) string { return "not implemented" }, + "tpl": func(string, any) any { return "not implemented" }, + "required": func(string, any) (any, error) { return "not implemented", nil }, // Provide a placeholder for the "lookup" function, which requires a kubernetes // connection. - "lookup": func(string, string, string, string) (map[string]interface{}, error) { - return map[string]interface{}{}, nil + "lookup": func(string, string, string, string) (map[string]any, error) { + return map[string]any{}, nil }, } @@ -83,7 +83,7 @@ func funcMap() template.FuncMap { // always return a string, even on marshal error (empty string). // // This is designed to be called from a template. -func toYAML(v interface{}) string { +func toYAML(v any) string { data, err := yaml.Marshal(v) if err != nil { // Swallow errors inside of a template. @@ -97,7 +97,7 @@ func toYAML(v interface{}) string { // // This is designed to be called from a template when need to ensure that the // output YAML is valid. -func mustToYAML(v interface{}) string { +func mustToYAML(v any) string { data, err := yaml.Marshal(v) if err != nil { panic(err) @@ -105,7 +105,7 @@ func mustToYAML(v interface{}) string { return strings.TrimSuffix(string(data), "\n") } -func toYAMLPretty(v interface{}) string { +func toYAMLPretty(v any) string { var data bytes.Buffer encoder := goYaml.NewEncoder(&data) encoder.SetIndent(2) @@ -124,8 +124,8 @@ func toYAMLPretty(v interface{}) string { // YAML documents. Additionally, because its intended use is within templates // it tolerates errors. It will insert the returned error message string into // m["Error"] in the returned map. -func fromYAML(str string) map[string]interface{} { - m := map[string]interface{}{} +func fromYAML(str string) map[string]any { + m := map[string]any{} if err := yaml.Unmarshal([]byte(str), &m); err != nil { m["Error"] = err.Error() @@ -139,11 +139,11 @@ func fromYAML(str string) map[string]interface{} { // YAML documents. Additionally, because its intended use is within templates // it tolerates errors. It will insert the returned error message string as // the first and only item in the returned array. -func fromYAMLArray(str string) []interface{} { - a := []interface{}{} +func fromYAMLArray(str string) []any { + a := []any{} if err := yaml.Unmarshal([]byte(str), &a); err != nil { - a = []interface{}{err.Error()} + a = []any{err.Error()} } return a } @@ -152,7 +152,7 @@ func fromYAMLArray(str string) []interface{} { // always return a string, even on marshal error (empty string). // // This is designed to be called from a template. -func toTOML(v interface{}) string { +func toTOML(v any) string { b := bytes.NewBuffer(nil) e := toml.NewEncoder(b) err := e.Encode(v) @@ -168,8 +168,8 @@ func toTOML(v interface{}) string { // TOML documents. Additionally, because its intended use is within templates // it tolerates errors. It will insert the returned error message string into // m["Error"] in the returned map. -func fromTOML(str string) map[string]interface{} { - m := make(map[string]interface{}) +func fromTOML(str string) map[string]any { + m := make(map[string]any) if err := toml.Unmarshal([]byte(str), &m); err != nil { m["Error"] = err.Error() @@ -181,7 +181,7 @@ func fromTOML(str string) map[string]interface{} { // always return a string, even on marshal error (empty string). // // This is designed to be called from a template. -func toJSON(v interface{}) string { +func toJSON(v any) string { data, err := json.Marshal(v) if err != nil { // Swallow errors inside of a template. @@ -195,7 +195,7 @@ func toJSON(v interface{}) string { // // This is designed to be called from a template when need to ensure that the // output JSON is valid. -func mustToJSON(v interface{}) string { +func mustToJSON(v any) string { data, err := json.Marshal(v) if err != nil { panic(err) @@ -209,8 +209,8 @@ func mustToJSON(v interface{}) string { // JSON documents. Additionally, because its intended use is within templates // it tolerates errors. It will insert the returned error message string into // m["Error"] in the returned map. -func fromJSON(str string) map[string]interface{} { - m := make(map[string]interface{}) +func fromJSON(str string) map[string]any { + m := make(map[string]any) if err := json.Unmarshal([]byte(str), &m); err != nil { m["Error"] = err.Error() @@ -224,11 +224,11 @@ func fromJSON(str string) map[string]interface{} { // JSON documents. Additionally, because its intended use is within templates // it tolerates errors. It will insert the returned error message string as // the first and only item in the returned array. -func fromJSONArray(str string) []interface{} { - a := []interface{}{} +func fromJSONArray(str string) []any { + a := []any{} if err := json.Unmarshal([]byte(str), &a); err != nil { - a = []interface{}{err.Error()} + a = []any{err.Error()} } return a } diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go index 71a72e2e4..48202454e 100644 --- a/pkg/engine/funcs_test.go +++ b/pkg/engine/funcs_test.go @@ -28,19 +28,19 @@ func TestFuncs(t *testing.T) { //TODO write tests for failure cases tests := []struct { tpl, expect string - vars interface{} + vars any }{{ tpl: `{{ toYaml . }}`, expect: `foo: bar`, - vars: map[string]interface{}{"foo": "bar"}, + vars: map[string]any{"foo": "bar"}, }, { tpl: `{{ toYamlPretty . }}`, expect: "baz:\n - 1\n - 2\n - 3", - vars: map[string]interface{}{"baz": []int{1, 2, 3}}, + vars: map[string]any{"baz": []int{1, 2, 3}}, }, { tpl: `{{ toToml . }}`, expect: "foo = \"bar\"\n", - vars: map[string]interface{}{"foo": "bar"}, + vars: map[string]any{"foo": "bar"}, }, { tpl: `{{ fromToml . }}`, expect: "map[hello:world]", @@ -68,7 +68,7 @@ keyInElement1 = "valueInElement1"`, }, { tpl: `{{ toJson . }}`, expect: `{"foo":"bar"}`, - vars: map[string]interface{}{"foo": "bar"}, + vars: map[string]any{"foo": "bar"}, }, { tpl: `{{ fromYaml . }}`, expect: "map[hello:world]", @@ -109,11 +109,11 @@ keyInElement1 = "valueInElement1"`, }, { tpl: `{{ merge .dict (fromYaml .yaml) }}`, expect: `map[a:map[b:c]]`, - vars: map[string]interface{}{"dict": map[string]interface{}{"a": map[string]interface{}{"b": "c"}}, "yaml": `{"a":{"b":"d"}}`}, + vars: map[string]any{"dict": map[string]any{"a": map[string]any{"b": "c"}}, "yaml": `{"a":{"b":"d"}}`}, }, { tpl: `{{ merge (fromYaml .yaml) .dict }}`, expect: `map[a:map[b:d]]`, - vars: map[string]interface{}{"dict": map[string]interface{}{"a": map[string]interface{}{"b": "c"}}, "yaml": `{"a":{"b":"d"}}`}, + vars: map[string]any{"dict": map[string]any{"a": map[string]any{"b": "c"}}, "yaml": `{"a":{"b":"d"}}`}, }, { tpl: `{{ fromYaml . }}`, expect: `map[Error:error unmarshaling JSON: while decoding JSON: json: cannot unmarshal array into Go value of type map[string]interface {}]`, @@ -136,15 +136,15 @@ keyInElement1 = "valueInElement1"`, assert.Equal(t, tt.expect, b.String(), tt.tpl) } - loopMap := map[string]interface{}{ + loopMap := map[string]any{ "foo": "bar", } - loopMap["loop"] = []interface{}{loopMap} + loopMap["loop"] = []any{loopMap} mustFuncsTests := []struct { tpl string - expect interface{} - vars interface{} + expect any + vars any }{{ tpl: `{{ mustToYaml . }}`, vars: loopMap, @@ -186,34 +186,34 @@ keyInElement1 = "valueInElement1"`, // be used to accidentally update mergo. This test and message should catch // the problem and explain why it's happening. func TestMerge(t *testing.T) { - dict := map[string]interface{}{ - "src2": map[string]interface{}{ + dict := map[string]any{ + "src2": map[string]any{ "h": 10, "i": "i", "j": "j", }, - "src1": map[string]interface{}{ + "src1": map[string]any{ "a": 1, "b": 2, - "d": map[string]interface{}{ + "d": map[string]any{ "e": "four", }, "g": []int{6, 7}, "i": "aye", "j": "jay", - "k": map[string]interface{}{ + "k": map[string]any{ "l": false, }, }, - "dst": map[string]interface{}{ + "dst": map[string]any{ "a": "one", "c": 3, - "d": map[string]interface{}{ + "d": map[string]any{ "f": 5, }, "g": []int{8, 9}, "i": "eye", - "k": map[string]interface{}{ + "k": map[string]any{ "l": true, }, }, @@ -223,11 +223,11 @@ func TestMerge(t *testing.T) { err := template.Must(template.New("test").Funcs(funcMap()).Parse(tpl)).Execute(&b, dict) assert.NoError(t, err) - expected := map[string]interface{}{ + expected := map[string]any{ "a": "one", // key overridden "b": 2, // merged from src1 "c": 3, // merged from dst - "d": map[string]interface{}{ // deep merge + "d": map[string]any{ // deep merge "e": "four", "f": 5, }, @@ -235,7 +235,7 @@ func TestMerge(t *testing.T) { "h": 10, // merged from src2 "i": "eye", // overridden twice "j": "jay", // overridden and merged - "k": map[string]interface{}{ + "k": map[string]any{ "l": true, // overridden }, } diff --git a/pkg/strvals/literal_parser.go b/pkg/strvals/literal_parser.go index d5d4c25b4..a364a62c4 100644 --- a/pkg/strvals/literal_parser.go +++ b/pkg/strvals/literal_parser.go @@ -26,8 +26,8 @@ import ( // ParseLiteral parses a set line interpreting the value as a literal string. // // A set line is of the form name1=value1 -func ParseLiteral(s string) (map[string]interface{}, error) { - vals := map[string]interface{}{} +func ParseLiteral(s string) (map[string]any, error) { + vals := map[string]any{} scanner := bytes.NewBufferString(s) t := newLiteralParser(scanner, vals) err := t.parse() @@ -39,7 +39,7 @@ func ParseLiteral(s string) (map[string]interface{}, error) { // // If the strval string has a key that exists in dest, it overwrites the // dest version. -func ParseLiteralInto(s string, dest map[string]interface{}) error { +func ParseLiteralInto(s string, dest map[string]any) error { scanner := bytes.NewBufferString(s) t := newLiteralParser(scanner, dest) return t.parse() @@ -54,10 +54,10 @@ func ParseLiteralInto(s string, dest map[string]interface{}) error { // where data is the final parsed data from the parses with correct types type literalParser struct { sc *bytes.Buffer - data map[string]interface{} + data map[string]any } -func newLiteralParser(sc *bytes.Buffer, data map[string]interface{}) *literalParser { +func newLiteralParser(sc *bytes.Buffer, data map[string]any) *literalParser { return &literalParser{sc: sc, data: data} } @@ -88,7 +88,7 @@ func runesUntilLiteral(in io.RuneReader, stop map[rune]bool) ([]rune, rune, erro } } -func (t *literalParser) key(data map[string]interface{}, nestedNameLevel int) (reterr error) { +func (t *literalParser) key(data map[string]any, nestedNameLevel int) (reterr error) { defer func() { if r := recover(); r != nil { reterr = fmt.Errorf("unable to parse key: %s", r) @@ -120,9 +120,9 @@ func (t *literalParser) key(data map[string]interface{}, nestedNameLevel int) (r } // first, create or find the target map in the given data - inner := map[string]interface{}{} + inner := map[string]any{} if _, ok := data[string(key)]; ok { - inner = data[string(key)].(map[string]interface{}) + inner = data[string(key)].(map[string]any) } // recurse on sub-tree with remaining data @@ -144,9 +144,9 @@ func (t *literalParser) key(data map[string]interface{}, nestedNameLevel int) (r kk := string(key) // find or create target list - list := []interface{}{} + list := []any{} if _, ok := data[kk]; ok { - list = data[kk].([]interface{}) + list = data[kk].([]any) } // now we need to get the value after the ] @@ -169,7 +169,7 @@ func (t *literalParser) keyIndex() (int, error) { return strconv.Atoi(string(v)) } -func (t *literalParser) listItem(list []interface{}, i, nestedNameLevel int) ([]interface{}, error) { +func (t *literalParser) listItem(list []any, i, nestedNameLevel int) ([]any, error) { if i < 0 { return list, fmt.Errorf("negative %d index not allowed", i) } @@ -191,14 +191,14 @@ func (t *literalParser) listItem(list []interface{}, i, nestedNameLevel int) ([] case lastRune == '.': // we have a nested object. Send to t.key - inner := map[string]interface{}{} + inner := map[string]any{} if len(list) > i { var ok bool - inner, ok = list[i].(map[string]interface{}) + inner, ok = list[i].(map[string]any) if !ok { // We have indices out of order. Initialize empty value. - list[i] = map[string]interface{}{} - inner = list[i].(map[string]interface{}) + list[i] = map[string]any{} + inner = list[i].(map[string]any) } } @@ -215,12 +215,12 @@ func (t *literalParser) listItem(list []interface{}, i, nestedNameLevel int) ([] if err != nil { return list, fmt.Errorf("error parsing index: %w", err) } - var crtList []interface{} + var crtList []any if len(list) > i { // If nested list already exists, take the value of list to next cycle. existed := list[i] if existed != nil { - crtList = list[i].([]interface{}) + crtList = list[i].([]any) } } diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index 8eb761dce..cecaa2453 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -52,8 +52,8 @@ func ToYAML(s string) (string, error) { // Parse parses a set line. // // A set line is of the form name1=value1,name2=value2 -func Parse(s string) (map[string]interface{}, error) { - vals := map[string]interface{}{} +func Parse(s string) (map[string]any, error) { + vals := map[string]any{} scanner := bytes.NewBufferString(s) t := newParser(scanner, vals, false) err := t.parse() @@ -63,8 +63,8 @@ func Parse(s string) (map[string]interface{}, error) { // ParseString parses a set line and forces a string value. // // A set line is of the form name1=value1,name2=value2 -func ParseString(s string) (map[string]interface{}, error) { - vals := map[string]interface{}{} +func ParseString(s string) (map[string]any, error) { + vals := map[string]any{} scanner := bytes.NewBufferString(s) t := newParser(scanner, vals, true) err := t.parse() @@ -75,7 +75,7 @@ func ParseString(s string) (map[string]interface{}, error) { // // If the strval string has a key that exists in dest, it overwrites the // dest version. -func ParseInto(s string, dest map[string]interface{}) error { +func ParseInto(s string, dest map[string]any) error { scanner := bytes.NewBufferString(s) t := newParser(scanner, dest, false) return t.parse() @@ -87,8 +87,8 @@ func ParseInto(s string, dest map[string]interface{}) error { // // When the files at path1 and path2 contained "val1" and "val2" respectively, the set line is consumed as // name1=val1,name2=val2 -func ParseFile(s string, reader RunesValueReader) (map[string]interface{}, error) { - vals := map[string]interface{}{} +func ParseFile(s string, reader RunesValueReader) (map[string]any, error) { + vals := map[string]any{} scanner := bytes.NewBufferString(s) t := newFileParser(scanner, vals, reader) err := t.parse() @@ -98,7 +98,7 @@ func ParseFile(s string, reader RunesValueReader) (map[string]interface{}, error // ParseIntoString parses a strvals line and merges the result into dest. // // This method always returns a string as the value. -func ParseIntoString(s string, dest map[string]interface{}) error { +func ParseIntoString(s string, dest map[string]any) error { scanner := bytes.NewBufferString(s) t := newParser(scanner, dest, true) return t.parse() @@ -109,7 +109,7 @@ func ParseIntoString(s string, dest map[string]interface{}) error { // An empty val is treated as null. // // If a key exists in dest, the new value overwrites the dest version. -func ParseJSON(s string, dest map[string]interface{}) error { +func ParseJSON(s string, dest map[string]any) error { scanner := bytes.NewBufferString(s) t := newJSONParser(scanner, dest) return t.parse() @@ -118,7 +118,7 @@ func ParseJSON(s string, dest map[string]interface{}) error { // ParseIntoFile parses a filevals line and merges the result into dest. // // This method always returns a string as the value. -func ParseIntoFile(s string, dest map[string]interface{}, reader RunesValueReader) error { +func ParseIntoFile(s string, dest map[string]any, reader RunesValueReader) error { scanner := bytes.NewBufferString(s) t := newFileParser(scanner, dest, reader) return t.parse() @@ -126,7 +126,7 @@ func ParseIntoFile(s string, dest map[string]interface{}, reader RunesValueReade // RunesValueReader is a function that takes the given value (a slice of runes) // and returns the parsed value -type RunesValueReader func([]rune) (interface{}, error) +type RunesValueReader func([]rune) (any, error) // parser is a simple parser that takes a strvals line and parses it into a // map representation. @@ -135,23 +135,23 @@ type RunesValueReader func([]rune) (interface{}, error) // where data is the final parsed data from the parses with correct types type parser struct { sc *bytes.Buffer - data map[string]interface{} + data map[string]any reader RunesValueReader isjsonval bool } -func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *parser { - stringConverter := func(rs []rune) (interface{}, error) { +func newParser(sc *bytes.Buffer, data map[string]any, stringBool bool) *parser { + stringConverter := func(rs []rune) (any, error) { return typedVal(rs, stringBool), nil } return &parser{sc: sc, data: data, reader: stringConverter} } -func newJSONParser(sc *bytes.Buffer, data map[string]interface{}) *parser { +func newJSONParser(sc *bytes.Buffer, data map[string]any) *parser { return &parser{sc: sc, data: data, reader: nil, isjsonval: true} } -func newFileParser(sc *bytes.Buffer, data map[string]interface{}, reader RunesValueReader) *parser { +func newFileParser(sc *bytes.Buffer, data map[string]any, reader RunesValueReader) *parser { return &parser{sc: sc, data: data, reader: reader} } @@ -176,7 +176,7 @@ func runeSet(r []rune) map[rune]bool { return s } -func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr error) { +func (t *parser) key(data map[string]any, nestedNameLevel int) (reterr error) { defer func() { if r := recover(); r != nil { reterr = fmt.Errorf("unable to parse key: %s", r) @@ -200,9 +200,9 @@ func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr e } kk := string(k) // Find or create target list - list := []interface{}{} + list := []any{} if _, ok := data[kk]; ok { - list = data[kk].([]interface{}) + list = data[kk].([]any) } // Now we need to get the value after the ]. @@ -224,7 +224,7 @@ func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr e // Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded, // we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we // discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset). - var jsonval interface{} + var jsonval any dec := json.NewDecoder(strings.NewReader(t.sc.String())) if err = dec.Decode(&jsonval); err != nil { return err @@ -270,9 +270,9 @@ func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr e } // First, create or find the target map. - inner := map[string]interface{}{} + inner := map[string]any{} if _, ok := data[string(k)]; ok { - inner = data[string(k)].(map[string]interface{}) + inner = data[string(k)].(map[string]any) } // Recurse @@ -288,7 +288,7 @@ func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr e } } -func set(data map[string]interface{}, key string, val interface{}) { +func set(data map[string]any, key string, val any) { // If key is empty, don't set it. if len(key) == 0 { return @@ -296,7 +296,7 @@ func set(data map[string]interface{}, key string, val interface{}) { data[key] = val } -func setIndex(list []interface{}, index int, val interface{}) (l2 []interface{}, err error) { +func setIndex(list []any, index int, val any) (l2 []any, err error) { // There are possible index values that are out of range on a target system // causing a panic. This will catch the panic and return an error instead. // The value of the index that causes a panic varies from system to system. @@ -313,7 +313,7 @@ func setIndex(list []interface{}, index int, val interface{}) (l2 []interface{}, return list, fmt.Errorf("index of %d is greater than maximum supported index of %d", index, MaxIndex) } if len(list) <= index { - newlist := make([]interface{}, index+1) + newlist := make([]any, index+1) copy(newlist, list) list = newlist } @@ -333,7 +333,7 @@ func (t *parser) keyIndex() (int, error) { } -func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interface{}, error) { +func (t *parser) listItem(list []any, i, nestedNameLevel int) ([]any, error) { if i < 0 { return list, fmt.Errorf("negative %d index not allowed", i) } @@ -357,7 +357,7 @@ func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interfa // Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded, // we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we // discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset). - var jsonval interface{} + var jsonval any dec := json.NewDecoder(strings.NewReader(t.sc.String())) if err = dec.Decode(&jsonval); err != nil { return list, err @@ -397,12 +397,12 @@ func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interfa if err != nil { return list, fmt.Errorf("error parsing index: %w", err) } - var crtList []interface{} + var crtList []any if len(list) > i { // If nested list already exists, take the value of list to next cycle. existed := list[i] if existed != nil { - crtList = list[i].([]interface{}) + crtList = list[i].([]any) } } // Now we need to get the value after the ]. @@ -413,14 +413,14 @@ func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interfa return setIndex(list, i, list2) case last == '.': // We have a nested object. Send to t.key - inner := map[string]interface{}{} + inner := map[string]any{} if len(list) > i { var ok bool - inner, ok = list[i].(map[string]interface{}) + inner, ok = list[i].(map[string]any) if !ok { // We have indices out of order. Initialize empty value. - list[i] = map[string]interface{}{} - inner = list[i].(map[string]interface{}) + list[i] = map[string]any{} + inner = list[i].(map[string]any) } } @@ -463,18 +463,18 @@ func (t *parser) val() ([]rune, error) { return v, err } -func (t *parser) valList() ([]interface{}, error) { +func (t *parser) valList() ([]any, error) { r, _, e := t.sc.ReadRune() if e != nil { - return []interface{}{}, e + return []any{}, e } if r != '{' { t.sc.UnreadRune() - return []interface{}{}, ErrNotList + return []any{}, ErrNotList } - list := []interface{}{} + list := []any{} stop := runeSet([]rune{',', '}'}) for { switch rs, last, err := runesUntil(t.sc, stop); { @@ -526,7 +526,7 @@ func inMap(k rune, m map[rune]bool) bool { return ok } -func typedVal(v []rune, st bool) interface{} { +func typedVal(v []rune, st bool) any { val := string(v) if st { From 5aac32077f87ed8cd80da1648abbd323320d4a0b Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Fri, 6 Feb 2026 18:29:59 +0000 Subject: [PATCH 167/571] feat(create): add --chart-api-version flag (when HELM_EXPERIMENTAL_CHART_V3 env var is set) (#31592) * feat(create): add hidden --chart-api-version flag Add --chart-api-version flag to helm create command to allow selecting chart API version (v2 or v3) when creating a new chart. - Default is v2 (existing behavior unchanged) - v3 uses internal/chart/v3 scaffold generator - Invalid versions return clear error message - Works with --starter flag Signed-off-by: Evans Mungai * Add HELM_EXPERIMENTAL_CHART_V3 feature gate to create command Signed-off-by: Evans Mungai * make chartv3 private and use loader to load the chart Signed-off-by: Evans Mungai * Hide chart-api-version flag until chart v3 is officially released Signed-off-by: Evans Mungai * Conditionally hide the --chart-api-version flag if chart v3 is not enabled Signed-off-by: Evans Mungai * Add internal gates package for internal feature gates Signed-off-by: Evans Mungai * Add doc for internal/gates package Signed-off-by: Evans Mungai --------- Signed-off-by: Evans Mungai --- internal/gates/doc.go | 19 +++ internal/gates/gates.go | 21 +++ pkg/cmd/create.go | 56 +++++++- pkg/cmd/create_test.go | 290 ++++++++++++++++++++++++++++------------ 4 files changed, 295 insertions(+), 91 deletions(-) create mode 100644 internal/gates/doc.go create mode 100644 internal/gates/gates.go diff --git a/internal/gates/doc.go b/internal/gates/doc.go new file mode 100644 index 000000000..09501e005 --- /dev/null +++ b/internal/gates/doc.go @@ -0,0 +1,19 @@ +/* +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 gates contains internal feature gates that can be used to enable or disable experimental features. +// This is a separate internal package instead of using the pkg/gates package to avoid circular dependencies. +package gates diff --git a/internal/gates/gates.go b/internal/gates/gates.go new file mode 100644 index 000000000..e071c54ea --- /dev/null +++ b/internal/gates/gates.go @@ -0,0 +1,21 @@ +/* +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 gates + +import "helm.sh/helm/v4/pkg/gates" + +// ChartV3 is the feature gate for chart API version v3. +const ChartV3 gates.Gate = "HELM_EXPERIMENTAL_CHART_V3" diff --git a/pkg/cmd/create.go b/pkg/cmd/create.go index 435c8ca82..b38ce6a12 100644 --- a/pkg/cmd/create.go +++ b/pkg/cmd/create.go @@ -23,6 +23,9 @@ import ( "github.com/spf13/cobra" + chartv3 "helm.sh/helm/v4/internal/chart/v3" + chartutilv3 "helm.sh/helm/v4/internal/chart/v3/util" + "helm.sh/helm/v4/internal/gates" chart "helm.sh/helm/v4/pkg/chart/v2" chartutil "helm.sh/helm/v4/pkg/chart/v2/util" "helm.sh/helm/v4/pkg/cmd/require" @@ -51,9 +54,10 @@ will be overwritten, but other files will be left alone. ` type createOptions struct { - starter string // --starter - name string - starterDir string + starter string // --starter + name string + starterDir string + chartAPIVersion string // --chart-api-version } func newCreateCmd(out io.Writer) *cobra.Command { @@ -81,12 +85,32 @@ func newCreateCmd(out io.Writer) *cobra.Command { } cmd.Flags().StringVarP(&o.starter, "starter", "p", "", "the name or absolute path to Helm starter scaffold") + cmd.Flags().StringVar(&o.chartAPIVersion, "chart-api-version", chart.APIVersionV2, "chart API version to use (v2 or v3)") + + if !gates.ChartV3.IsEnabled() { + cmd.Flags().MarkHidden("chart-api-version") + } + return cmd } func (o *createOptions) run(out io.Writer) error { fmt.Fprintf(out, "Creating %s\n", o.name) + switch o.chartAPIVersion { + case chart.APIVersionV2, "": + return o.createV2Chart(out) + case chartv3.APIVersionV3: + if !gates.ChartV3.IsEnabled() { + return gates.ChartV3.Error() + } + return o.createV3Chart(out) + default: + return fmt.Errorf("unsupported chart API version: %s (supported: v2, v3)", o.chartAPIVersion) + } +} + +func (o *createOptions) createV2Chart(out io.Writer) error { chartname := filepath.Base(o.name) cfile := &chart.Metadata{ Name: chartname, @@ -111,3 +135,29 @@ func (o *createOptions) run(out io.Writer) error { _, err := chartutil.Create(chartname, filepath.Dir(o.name)) return err } + +func (o *createOptions) createV3Chart(out io.Writer) error { + chartname := filepath.Base(o.name) + cfile := &chartv3.Metadata{ + Name: chartname, + Description: "A Helm chart for Kubernetes", + Type: "application", + Version: "0.1.0", + AppVersion: "0.1.0", + APIVersion: chartv3.APIVersionV3, + } + + if o.starter != "" { + // Create from the starter + lstarter := filepath.Join(o.starterDir, o.starter) + // If path is absolute, we don't want to prefix it with helm starters folder + if filepath.IsAbs(o.starter) { + lstarter = o.starter + } + return chartutilv3.CreateFrom(cfile, filepath.Dir(o.name), lstarter) + } + + chartutilv3.Stderr = out + _, err := chartutilv3.Create(chartname, filepath.Dir(o.name)) + return err +} diff --git a/pkg/cmd/create_test.go b/pkg/cmd/create_test.go index 90ed90eff..57e2aaf5f 100644 --- a/pkg/cmd/create_test.go +++ b/pkg/cmd/create_test.go @@ -22,9 +22,13 @@ import ( "path/filepath" "testing" + chartv3 "helm.sh/helm/v4/internal/chart/v3" + chartutilv3 "helm.sh/helm/v4/internal/chart/v3/util" + "helm.sh/helm/v4/internal/gates" "helm.sh/helm/v4/internal/test/ensure" - chart "helm.sh/helm/v4/pkg/chart/v2" - "helm.sh/helm/v4/pkg/chart/v2/loader" + chart "helm.sh/helm/v4/pkg/chart" + chartloader "helm.sh/helm/v4/pkg/chart/loader" + chartv2 "helm.sh/helm/v4/pkg/chart/v2" chartutil "helm.sh/helm/v4/pkg/chart/v2/util" "helm.sh/helm/v4/pkg/helmpath" ) @@ -46,41 +50,171 @@ func TestCreateCmd(t *testing.T) { t.Fatalf("chart is not directory") } - c, err := loader.LoadDir(cname) + c, err := chartloader.LoadDir(cname) if err != nil { t.Fatal(err) } - if c.Name() != cname { - t.Errorf("Expected %q name, got %q", cname, c.Name()) + acc, err := chart.NewAccessor(c) + if err != nil { + t.Fatal(err) + } + + if acc.Name() != cname { + t.Errorf("Expected %q name, got %q", cname, acc.Name()) + } + metadata := acc.MetadataAsMap() + apiVersion, ok := metadata["APIVersion"].(string) + if !ok { + t.Fatal("APIVersion not found in metadata") } - if c.Metadata.APIVersion != chart.APIVersionV2 { - t.Errorf("Wrong API version: %q", c.Metadata.APIVersion) + if apiVersion != chartv2.APIVersionV2 { + t.Errorf("Wrong API version: %q", apiVersion) } } func TestCreateStarterCmd(t *testing.T) { + tests := []struct { + name string + chartAPIVersion string + useAbsolutePath bool + expectedVersion string + }{ + { + name: "v2 with relative starter path", + chartAPIVersion: "", + useAbsolutePath: false, + expectedVersion: chartv2.APIVersionV2, + }, + { + name: "v2 with absolute starter path", + chartAPIVersion: "", + useAbsolutePath: true, + expectedVersion: chartv2.APIVersionV2, + }, + { + name: "v3 with relative starter path", + chartAPIVersion: "v3", + useAbsolutePath: false, + expectedVersion: chartv3.APIVersionV3, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Chdir(t.TempDir()) + ensure.HelmHome(t) + defer resetEnv()() + + // Enable feature gate for v3 charts + if tt.chartAPIVersion == "v3" { + t.Setenv(string(gates.ChartV3), "1") + } + + cname := "testchart" + + // Create a starter using the appropriate chartutil + starterchart := helmpath.DataPath("starters") + os.MkdirAll(starterchart, 0o755) + var err error + var dest string + if tt.chartAPIVersion == "v3" { + dest, err = chartutilv3.Create("starterchart", starterchart) + } else { + dest, err = chartutil.Create("starterchart", starterchart) + } + if err != nil { + t.Fatalf("Could not create chart: %s", err) + } + t.Logf("Created %s", dest) + + tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") + if err := os.WriteFile(tplpath, []byte("test"), 0o644); err != nil { + t.Fatalf("Could not write template: %s", err) + } + + // Build the command + starterArg := "starterchart" + if tt.useAbsolutePath { + starterArg = filepath.Join(starterchart, "starterchart") + } + cmd := fmt.Sprintf("create --starter=%s", starterArg) + if tt.chartAPIVersion == "v3" { + cmd += fmt.Sprintf(" --chart-api-version=%s", chartv3.APIVersionV3) + } else { + cmd += fmt.Sprintf(" --chart-api-version=%s", chartv2.APIVersionV2) + } + cmd += " " + cname + + // Run create + if _, _, err := executeActionCommand(cmd); err != nil { + t.Fatalf("Failed to run create: %s", err) + } + + // Test that the chart is there + if fi, err := os.Stat(cname); err != nil { + t.Fatalf("no chart directory: %s", err) + } else if !fi.IsDir() { + t.Fatalf("chart is not directory") + } + + // Load and verify the chart + c, err := chartloader.LoadDir(cname) + if err != nil { + t.Fatal(err) + } + + acc, err := chart.NewAccessor(c) + if err != nil { + t.Fatal(err) + } + + chartName := acc.Name() + metadata := acc.MetadataAsMap() + apiVersion, ok := metadata["APIVersion"].(string) + if !ok { + t.Fatal("APIVersion not found in metadata") + } + var templates []string + for _, tpl := range acc.Templates() { + templates = append(templates, tpl.Name) + } + + if chartName != cname { + t.Errorf("Expected %q name, got %q", cname, chartName) + } + if apiVersion != tt.expectedVersion { + t.Errorf("Wrong API version: expected %q, got %q", tt.expectedVersion, apiVersion) + } + + // Verify custom template exists + found := false + for _, name := range templates { + if name == "templates/foo.tpl" { + found = true + break + } + } + if !found { + t.Error("Did not find foo.tpl") + } + }) + } +} + +func TestCreateFileCompletion(t *testing.T) { + checkFileCompletion(t, "create", true) + checkFileCompletion(t, "create myname", false) +} + +func TestCreateCmdChartAPIVersionV2(t *testing.T) { t.Chdir(t.TempDir()) ensure.HelmHome(t) cname := "testchart" - defer resetEnv()() - // Create a starter. - starterchart := helmpath.DataPath("starters") - os.MkdirAll(starterchart, 0o755) - if dest, err := chartutil.Create("starterchart", starterchart); err != nil { - t.Fatalf("Could not create chart: %s", err) - } else { - t.Logf("Created %s", dest) - } - tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") - if err := os.WriteFile(tplpath, []byte("test"), 0o644); err != nil { - t.Fatalf("Could not write template: %s", err) - } - // Run a create - if _, _, err := executeActionCommand(fmt.Sprintf("create --starter=starterchart %s", cname)); err != nil { - t.Errorf("Failed to run create: %s", err) - return + // Run a create with explicit v2 + if _, _, err := executeActionCommand("create --chart-api-version=v2 " + cname); err != nil { + t.Fatalf("Failed to run create: %s", err) } // Test that the chart is there @@ -90,62 +224,38 @@ func TestCreateStarterCmd(t *testing.T) { t.Fatalf("chart is not directory") } - c, err := loader.LoadDir(cname) + c, err := chartloader.LoadDir(cname) if err != nil { t.Fatal(err) } - if c.Name() != cname { - t.Errorf("Expected %q name, got %q", cname, c.Name()) - } - if c.Metadata.APIVersion != chart.APIVersionV2 { - t.Errorf("Wrong API version: %q", c.Metadata.APIVersion) + acc, err := chart.NewAccessor(c) + if err != nil { + t.Fatal(err) } - expectedNumberOfTemplates := 10 - if l := len(c.Templates); l != expectedNumberOfTemplates { - t.Errorf("Expected %d templates, got %d", expectedNumberOfTemplates, l) + if acc.Name() != cname { + t.Errorf("Expected %q name, got %q", cname, acc.Name()) } - - found := false - for _, tpl := range c.Templates { - if tpl.Name == "templates/foo.tpl" { - found = true - if data := string(tpl.Data); data != "test" { - t.Errorf("Expected template 'test', got %q", data) - } - } + metadata := acc.MetadataAsMap() + apiVersion, ok := metadata["APIVersion"].(string) + if !ok { + t.Fatal("APIVersion not found in metadata") } - if !found { - t.Error("Did not find foo.tpl") + if apiVersion != chartv2.APIVersionV2 { + t.Errorf("Wrong API version: expected %q, got %q", chartv2.APIVersionV2, apiVersion) } } -func TestCreateStarterAbsoluteCmd(t *testing.T) { +func TestCreateCmdChartAPIVersionV3(t *testing.T) { t.Chdir(t.TempDir()) - defer resetEnv()() ensure.HelmHome(t) + t.Setenv(string(gates.ChartV3), "1") cname := "testchart" - // Create a starter. - starterchart := helmpath.DataPath("starters") - os.MkdirAll(starterchart, 0o755) - if dest, err := chartutil.Create("starterchart", starterchart); err != nil { - t.Fatalf("Could not create chart: %s", err) - } else { - t.Logf("Created %s", dest) - } - tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") - if err := os.WriteFile(tplpath, []byte("test"), 0o644); err != nil { - t.Fatalf("Could not write template: %s", err) - } - - starterChartPath := filepath.Join(starterchart, "starterchart") - - // Run a create - if _, _, err := executeActionCommand(fmt.Sprintf("create --starter=%s %s", starterChartPath, cname)); err != nil { - t.Errorf("Failed to run create: %s", err) - return + // Run a create with v3 + if _, _, err := executeActionCommand("create --chart-api-version=v3 " + cname); err != nil { + t.Fatalf("Failed to run create: %s", err) } // Test that the chart is there @@ -155,38 +265,42 @@ func TestCreateStarterAbsoluteCmd(t *testing.T) { t.Fatalf("chart is not directory") } - c, err := loader.LoadDir(cname) + c, err := chartloader.LoadDir(cname) if err != nil { t.Fatal(err) } - if c.Name() != cname { - t.Errorf("Expected %q name, got %q", cname, c.Name()) - } - if c.Metadata.APIVersion != chart.APIVersionV2 { - t.Errorf("Wrong API version: %q", c.Metadata.APIVersion) + acc, err := chart.NewAccessor(c) + if err != nil { + t.Fatal(err) } - expectedNumberOfTemplates := 10 - if l := len(c.Templates); l != expectedNumberOfTemplates { - t.Errorf("Expected %d templates, got %d", expectedNumberOfTemplates, l) + if acc.Name() != cname { + t.Errorf("Expected %q name, got %q", cname, acc.Name()) } - - found := false - for _, tpl := range c.Templates { - if tpl.Name == "templates/foo.tpl" { - found = true - if data := string(tpl.Data); data != "test" { - t.Errorf("Expected template 'test', got %q", data) - } - } + metadata := acc.MetadataAsMap() + apiVersion, ok := metadata["APIVersion"].(string) + if !ok { + t.Fatal("APIVersion not found in metadata") } - if !found { - t.Error("Did not find foo.tpl") + if apiVersion != chartv3.APIVersionV3 { + t.Errorf("Wrong API version: expected %q, got %q", chartv3.APIVersionV3, apiVersion) } } -func TestCreateFileCompletion(t *testing.T) { - checkFileCompletion(t, "create", true) - checkFileCompletion(t, "create myname", false) +func TestCreateCmdInvalidChartAPIVersion(t *testing.T) { + t.Chdir(t.TempDir()) + ensure.HelmHome(t) + cname := "testchart" + + // Run a create with invalid version + _, _, err := executeActionCommand("create --chart-api-version=v1 " + cname) + if err == nil { + t.Fatal("Expected error for invalid API version, got nil") + } + + expectedErr := "unsupported chart API version: v1 (supported: v2, v3)" + if err.Error() != expectedErr { + t.Errorf("Expected error %q, got %q", expectedErr, err.Error()) + } } From 9817a68618245370e98e09d7f06c7cc1cefe8a62 Mon Sep 17 00:00:00 2001 From: Manuel Alonso <434575+manute@users.noreply.github.com> Date: Sat, 7 Feb 2026 11:27:24 +0100 Subject: [PATCH 168/571] fix(install): check nil for restClientGetter and fix tests Signed-off-by: Manuel Alonso <434575+manute@users.noreply.github.com> --- pkg/action/install.go | 39 +++++++++-------- pkg/action/install_test.go | 89 ++++++++++++++++++++++++++++++++------ 2 files changed, 96 insertions(+), 32 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index b019771e1..c37f3e4d1 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -234,27 +234,30 @@ func (i *Install) installCRDs(crds []chart.CRD) error { // the case when an action configuration is reused for multiple actions, // as otherwise it is later loaded by ourselves when getCapabilities // is called later on in the installation process. - if i.cfg.Capabilities != nil { - discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() + if i.cfg.RESTClientGetter != nil { + if i.cfg.Capabilities != nil { + discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() + if err != nil { + return err + } + + if discoveryClient != nil { + i.cfg.Logger().Debug("clearing discovery cache") + discoveryClient.Invalidate() + _, _ = discoveryClient.ServerGroups() + } + } + + // Invalidate the REST mapper, since it will not have the new CRDs + // present. + restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper() if err != nil { return err } - - i.cfg.Logger().Debug("clearing discovery cache") - discoveryClient.Invalidate() - - _, _ = discoveryClient.ServerGroups() - } - - // Invalidate the REST mapper, since it will not have the new CRDs - // present. - restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper() - if err != nil { - return err - } - if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { - i.cfg.Logger().Debug("clearing REST mapper cache") - resettable.Reset() + if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { + i.cfg.Logger().Debug("clearing REST mapper cache") + resettable.Reset() + } } } return nil diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 4b9b19d5d..52dd83788 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -35,6 +35,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kuberuntime "k8s.io/apimachinery/pkg/runtime" @@ -44,7 +45,6 @@ import ( "k8s.io/client-go/rest/fake" ci "helm.sh/helm/v4/pkg/chart" - "helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/pkg/chart/common" @@ -111,6 +111,54 @@ func createDummyResourceList(owned bool) kube.ResourceList { return resourceList } +func createDummyCRDList(owned bool) kube.ResourceList { + obj := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummyName", + Namespace: "spaced", + }, + } + + if owned { + obj.Labels = map[string]string{ + "app.kubernetes.io/managed-by": "Helm", + } + obj.Annotations = map[string]string{ + "meta.helm.sh/release-name": "test-install-release", + "meta.helm.sh/release-namespace": "spaced", + } + } + + resInfo := resource.Info{ + Name: "dummyName", + Namespace: "spaced", + Mapping: &meta.RESTMapping{ + Resource: schema.GroupVersionResource{Group: "test", Version: "v1", Resource: "crd"}, + GroupVersionKind: schema.GroupVersionKind{Group: "test", Version: "v1", Kind: "crd"}, + Scope: meta.RESTScopeNamespace, + }, + Object: obj, + } + body := io.NopCloser(bytes.NewReader([]byte(kuberuntime.EncodeOrDie(appsv1Codec, obj)))) + + resInfo.Client = &fake.RESTClient{ + GroupVersion: schema.GroupVersion{Group: "test", Version: "v1"}, + NegotiatedSerializer: scheme.Codecs.WithoutConversion(), + Client: fake.CreateHTTPClient(func(_ *http.Request) (*http.Response, error) { + header := http.Header{} + header.Set("Content-Type", kuberuntime.ContentTypeJSON) + return &http.Response{ + StatusCode: http.StatusOK, + Header: header, + Body: body, + }, nil + }), + } + var resourceList kube.ResourceList + resourceList.Append(&resInfo) + return resourceList +} + func installActionWithConfig(config *Configuration) *Install { instAction := NewInstall(config) instAction.Namespace = "spaced" @@ -1085,9 +1133,7 @@ func TestInstallSetRegistryClient(t *testing.T) { } func TestInstallCRDs(t *testing.T) { - config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false)) - config.RESTClientGetter = cli.New().RESTClientGetter() - + config := actionConfigFixtureWithDummyResources(t, createDummyCRDList(false)) instAction := NewInstall(config) mockFile := common.File{ @@ -1097,17 +1143,32 @@ func TestInstallCRDs(t *testing.T) { mockChart := buildChart(withFile(mockFile)) crdsToInstall := mockChart.CRDObjects() - t.Run("fresh installation", func(t *testing.T) { - assert.Len(t, crdsToInstall, 1) - assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) - require.NoError(t, instAction.installCRDs(crdsToInstall)) - }) + assert.Len(t, crdsToInstall, 1) + assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) + require.NoError(t, instAction.installCRDs(crdsToInstall)) +} - t.Run("already exist", func(t *testing.T) { - assert.Len(t, crdsToInstall, 1) - assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) - require.NoError(t, instAction.installCRDs(crdsToInstall)) - }) +func TestInstallCRDs_AlreadyExist(t *testing.T) { + dummyResources := createDummyCRDList(false) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: dummyResources} + mockError := &apierrors.StatusError{ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Reason: metav1.StatusReasonAlreadyExists, + }} + failingKubeClient.CreateError = mockError + + config := actionConfigFixtureWithDummyResources(t, dummyResources) + config.KubeClient = &failingKubeClient + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + + assert.Nil(t, instAction.installCRDs(crdsToInstall)) } func TestInstallCRDs_KubeClient_BuildError(t *testing.T) { From 76eb37c01aaece271343039f44d7803017dd5c81 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Mon, 11 Aug 2025 09:08:43 +0300 Subject: [PATCH 169/571] fix(downloader): safely handle concurrent file writes on Windows When multiple processes try to download the same chart version concurrently (e.g., via Terraform), they can race to write the destination file. On Windows, this results in 'Access Denied' errors because the file cannot be renamed while another process has a handle to the destination. This commit introduces 'PlatformAtomicWriteFile' to the fileutil package. On Unix-like systems, it simply delegates to AtomicWriteFile, maintaining existing behavior. On Windows, it coordinates writes using a lock file (.lock). It acquires the lock, then performs the atomic write. Crucially, this implementation ensures that existing files are overwritten (rather than skipped). This ensures that if a chart is republished with the same version, the local cache is correctly updated, preventing stale data issues. Fixes #31633 Signed-off-by: Orgad Shaneh --- internal/fileutil/fileutil_test.go | 26 ++++ internal/fileutil/fileutil_unix.go | 32 +++++ internal/fileutil/fileutil_windows.go | 54 ++++++++ pkg/downloader/chart_downloader.go | 10 +- .../chart_downloader_windows_test.go | 131 ++++++++++++++++++ 5 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 internal/fileutil/fileutil_unix.go create mode 100644 internal/fileutil/fileutil_windows.go create mode 100644 pkg/downloader/chart_downloader_windows_test.go diff --git a/internal/fileutil/fileutil_test.go b/internal/fileutil/fileutil_test.go index 881fbb49d..71fcae177 100644 --- a/internal/fileutil/fileutil_test.go +++ b/internal/fileutil/fileutil_test.go @@ -119,3 +119,29 @@ func TestAtomicWriteFile_LargeContent(t *testing.T) { t.Fatalf("expected large content to match, got different length: %d vs %d", len(largeContent), len(got)) } } + +// TestPlatformAtomicWriteFile_OverwritesExisting verifies that the platform +// helper replaces existing files instead of silently skipping them. +func TestPlatformAtomicWriteFile_OverwritesExisting(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "overwrite_test") + + first := bytes.NewReader([]byte("first")) + if err := PlatformAtomicWriteFile(path, first, 0644); err != nil { + t.Fatalf("first write failed: %v", err) + } + + second := bytes.NewReader([]byte("second")) + if err := PlatformAtomicWriteFile(path, second, 0644); err != nil { + t.Fatalf("second write failed: %v", err) + } + + contents, err := os.ReadFile(path) + if err != nil { + t.Fatalf("failed reading result: %v", err) + } + + if string(contents) != "second" { + t.Fatalf("expected file to be overwritten, got %q", string(contents)) + } +} diff --git a/internal/fileutil/fileutil_unix.go b/internal/fileutil/fileutil_unix.go new file mode 100644 index 000000000..bbacb10bf --- /dev/null +++ b/internal/fileutil/fileutil_unix.go @@ -0,0 +1,32 @@ +//go:build !windows + +/* +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 fileutil + +import ( + "io" + "os" +) + +// PlatformAtomicWriteFile atomically writes a file to disk. +// +// On non-Windows platforms we don't need extra coordination, so this simply +// delegates to AtomicWriteFile to preserve the existing overwrite behaviour. +func PlatformAtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error { + return AtomicWriteFile(filename, reader, mode) +} diff --git a/internal/fileutil/fileutil_windows.go b/internal/fileutil/fileutil_windows.go new file mode 100644 index 000000000..179237860 --- /dev/null +++ b/internal/fileutil/fileutil_windows.go @@ -0,0 +1,54 @@ +//go:build windows + +/* +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 fileutil + +import ( + "io" + "os" + + "github.com/gofrs/flock" +) + +// PlatformAtomicWriteFile atomically writes a file to disk with file locking to +// prevent concurrent writes. This is particularly useful on Windows where +// concurrent writes to the same file can cause "Access Denied" errors. +// +// The function acquires a lock on the target file and performs an atomic write, +// preserving the existing behaviour of overwriting any previous content once +// the lock is obtained. +func PlatformAtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error { + // Use a separate lock file to coordinate access between processes + // We cannot lock the target file directly as it would prevent the atomic rename + lockFileName := filename + ".lock" + fileLock := flock.New(lockFileName) + + // Lock() ensures serialized access - if another process is writing, this will wait + if err := fileLock.Lock(); err != nil { + return err + } + defer func() { + fileLock.Unlock() + // Clean up the lock file + // Ignore errors as the file might not exist or be in use by another process + os.Remove(lockFileName) + }() + + // Perform the atomic write while holding the lock + return AtomicWriteFile(filename, reader, mode) +} diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index ee4f8abe3..ea178dfe9 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -156,7 +156,11 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven } destfile := filepath.Join(dest, name) - if err := fileutil.AtomicWriteFile(destfile, data, 0644); err != nil { + + // Use PlatformAtomicWriteFile to handle platform-specific concurrency concerns + // (Windows requires locking to avoid "Access Denied" errors when multiple + // processes write the same file) + if err := fileutil.PlatformAtomicWriteFile(destfile, data, 0644); err != nil { return destfile, nil, err } @@ -186,7 +190,9 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven } } provfile := destfile + ".prov" - if err := fileutil.AtomicWriteFile(provfile, body, 0644); err != nil { + + // Use PlatformAtomicWriteFile for the provenance file as well + if err := fileutil.PlatformAtomicWriteFile(provfile, body, 0644); err != nil { return destfile, nil, err } diff --git a/pkg/downloader/chart_downloader_windows_test.go b/pkg/downloader/chart_downloader_windows_test.go new file mode 100644 index 000000000..732416701 --- /dev/null +++ b/pkg/downloader/chart_downloader_windows_test.go @@ -0,0 +1,131 @@ +//go:build windows + +/* +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 downloader + +import ( + "os" + "path/filepath" + "sync" + "testing" + + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/repo/v1/repotest" +) + +// TestParallelDownloadTo tests that parallel downloads to the same file +// don't cause "Access Denied" errors on Windows. This test is Windows-specific +// because the file locking behavior is only needed on Windows. +func TestParallelDownloadTo(t *testing.T) { + // Set up a simple test server with a chart + srv := repotest.NewTempServer(t, repotest.WithChartSourceGlob("testdata/*.tgz")) + defer srv.Stop() + + if err := srv.CreateIndex(); err != nil { + t.Fatal(err) + } + + dest := t.TempDir() + cacheDir := t.TempDir() + + c := ChartDownloader{ + Out: os.Stderr, + RepositoryConfig: repoConfig, + RepositoryCache: repoCache, + ContentCache: cacheDir, + Cache: &DiskCache{Root: cacheDir}, + Getters: getter.All(&cli.EnvSettings{ + RepositoryConfig: repoConfig, + RepositoryCache: repoCache, + ContentCache: cacheDir, + }), + } + + // Use a direct URL to bypass repository lookup + chartURL := srv.URL() + "/local-subchart-0.1.0.tgz" + + // Number of parallel downloads to attempt + numDownloads := 10 + var wg sync.WaitGroup + errors := make([]error, numDownloads) + + // Launch multiple goroutines to download the same chart simultaneously + for i := 0; i < numDownloads; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + _, _, err := c.DownloadTo(chartURL, "", dest) + errors[index] = err + }(i) + } + + wg.Wait() + + // Check if any download failed + failedCount := 0 + for i, err := range errors { + if err != nil { + t.Logf("Download %d failed: %v", i, err) + failedCount++ + } + } + + // With the file locking fix, all parallel downloads should succeed + if failedCount > 0 { + t.Errorf("Parallel downloads failed: %d out of %d downloads failed due to concurrent file access", failedCount, numDownloads) + } + + // Verify the file exists and is valid + expectedFile := filepath.Join(dest, "local-subchart-0.1.0.tgz") + info, err := os.Stat(expectedFile) + if err != nil { + t.Errorf("Expected file %s does not exist: %v", expectedFile, err) + } else { + // Verify the file is not empty + if info.Size() == 0 { + t.Errorf("Downloaded file %s is empty (0 bytes)", expectedFile) + } + + // Verify the file has the expected size (should match the source file) + sourceFile := "testdata/local-subchart-0.1.0.tgz" + sourceInfo, err := os.Stat(sourceFile) + if err == nil && info.Size() != sourceInfo.Size() { + t.Errorf("Downloaded file size (%d bytes) doesn't match source file size (%d bytes)", + info.Size(), sourceInfo.Size()) + } + + // Verify it's a valid tar.gz file by checking the magic bytes + file, err := os.Open(expectedFile) + if err == nil { + defer file.Close() + // gzip magic bytes are 0x1f 0x8b + magic := make([]byte, 2) + if n, err := file.Read(magic); err == nil && n == 2 { + if magic[0] != 0x1f || magic[1] != 0x8b { + t.Errorf("Downloaded file is not a valid gzip file (magic bytes: %x)", magic) + } + } + } + + // Verify no lock file was left behind + lockFile := expectedFile + ".lock" + if _, err := os.Stat(lockFile); err == nil { + t.Errorf("Lock file %s was not cleaned up", lockFile) + } + } +} From 5638c35399464b6432ba81b92a341218991efa5c Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Sun, 8 Feb 2026 09:45:12 -0800 Subject: [PATCH 170/571] chore: new KEYS entry for George Jenkins Signed-off-by: George Jenkins --- KEYS | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/KEYS b/KEYS index e772fff40..21ca3487f 100644 --- a/KEYS +++ b/KEYS @@ -1058,3 +1058,23 @@ K6V08VpFmniENmCDHshXYq0gGiTDAP9FsXl2UtmFU5xuYxH4fRKIxgmxJRAFMWI8 u3Rdu/s+DQ== =smBO -----END PGP PUBLIC KEY BLOCK----- +pub ed25519 2026-02-08 [SC] + BF888333D96A1C18E2682AAED79D67C9EC016739 +uid [ultimate] George Jenkins +sig 3 D79D67C9EC016739 2026-02-08 [self-signature] +sub cv25519 2026-02-08 [E] +sig D79D67C9EC016739 2026-02-08 [self-signature] + +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEaYgDUBYJKwYBBAHaRw8BAQdAWKYkFrwgmfaY/hUq5Z2YpEy8WACKclo2eV/n +1ausaEy0JEdlb3JnZSBKZW5raW5zIDxndmplbmtpbnNAZ21haWwuY29tPoiTBBMW +CgA7FiEEv4iDM9lqHBjiaCqu151nyewBZzkFAmmIA1ACGwMFCwkIBwICIgIGFQoJ +CAsCBBYCAwECHgcCF4AACgkQ151nyewBZzlP0gD/ZFhm9FikdlZO5pW7xWR4YnP4 +yFAuY32G9dNdFn1x1p4BALR8Rtpp68eC9R8bq3/r1dK8gwig8DMWirdYaf2ePKoL +uDgEaYgDUBIKKwYBBAGXVQEFAQEHQJwM3R9CTypooHz/4w1waXAct8K2wA1bwi1r +yfb6uMMKAwEIB4h4BBgWCgAgFiEEv4iDM9lqHBjiaCqu151nyewBZzkFAmmIA1AC +GwwACgkQ151nyewBZzlgYAEAoVwYdoO6f3VwGukpv7RtKwF7PQC9AnBUx98TZZ6t +IaoA/RR14NXYYcd0fCwN6sFPq58/NbNkRHBrfw1CntxiJcYD +=duOC +-----END PGP PUBLIC KEY BLOCK----- From edbd705bd034246700cc0998016caa303cff42dc Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 9 Feb 2026 07:45:06 +0100 Subject: [PATCH 171/571] fix(cmd): errorlint linter #### Description errorlint linter in cmd/helm Signed-off-by: Matthieu MOREL --- cmd/helm/helm.go | 4 +++- cmd/helm/helm_test.go | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 66d342500..0c4f697b6 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -17,6 +17,7 @@ limitations under the License. package main // import "helm.sh/helm/v4/cmd/helm" import ( + "errors" "log/slog" "os" @@ -41,7 +42,8 @@ func main() { } if err := cmd.Execute(); err != nil { - if cerr, ok := err.(helmcmd.CommandError); ok { + var cerr helmcmd.CommandError + if errors.As(err, &cerr) { os.Exit(cerr.ExitCode) } os.Exit(1) diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index 0458e8037..60addadb1 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -18,6 +18,7 @@ package main import ( "bytes" + "errors" "os" "os/exec" "runtime" @@ -60,7 +61,8 @@ func TestCliPluginExitCode(t *testing.T) { cmd.Stderr = stderr err := cmd.Run() - exiterr, ok := err.(*exec.ExitError) + exiterr := &exec.ExitError{} + ok := errors.As(err, &exiterr) if !ok { t.Fatalf("Unexpected error type returned by os.Exit: %T", err) } From c8989d984ff69e8ad21b27d6ac6193dd3150b1a7 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 9 Feb 2026 07:45:21 +0100 Subject: [PATCH 172/571] fix(pkg): errorlint linter #### Description errorlint linter in pkg Signed-off-by: Matthieu MOREL --- pkg/action/package_test.go | 3 ++- pkg/cmd/upgrade.go | 3 ++- pkg/downloader/chart_downloader_test.go | 3 ++- pkg/repo/v1/index_test.go | 3 ++- pkg/storage/driver/sql_test.go | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go index 84dcb71c1..2e1d4ff07 100644 --- a/pkg/action/package_test.go +++ b/pkg/action/package_test.go @@ -17,6 +17,7 @@ limitations under the License. package action import ( + "errors" "os" "path" "testing" @@ -146,7 +147,7 @@ func TestValidateVersion(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := validateVersion(tt.args.ver); err != nil { - if err != tt.wantErr { + if !errors.Is(err, tt.wantErr) { t.Errorf("Expected {%v}, got {%v}", tt.wantErr, err) } diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index 918d6f5b8..b71c4ae2d 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -18,6 +18,7 @@ package cmd import ( "context" + "errors" "fmt" "io" "log" @@ -124,7 +125,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { histClient := action.NewHistory(cfg) histClient.Max = 1 versions, err := histClient.Run(args[0]) - if err == driver.ErrReleaseNotFound || isReleaseUninstalled(versions) { + if errors.Is(err, driver.ErrReleaseNotFound) || isReleaseUninstalled(versions) { // Only print this to stdout for table output if outfmt == output.Table { fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", args[0]) diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index 4349ecef9..777e21a6e 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -18,6 +18,7 @@ package downloader import ( "crypto/sha256" "encoding/hex" + "errors" "os" "path/filepath" "testing" @@ -376,7 +377,7 @@ func TestScanReposForURL(t *testing.T) { // A lookup failure should produce an ErrNoOwnerRepo u = "https://no.such.repo/foo/bar-1.23.4.tgz" - if _, err = c.scanReposForURL(u, rf); err != ErrNoOwnerRepo { + if _, err = c.scanReposForURL(u, rf); !errors.Is(err, ErrNoOwnerRepo) { t.Fatalf("expected ErrNoOwnerRepo, got %v", err) } } diff --git a/pkg/repo/v1/index_test.go b/pkg/repo/v1/index_test.go index 517457dc4..446160143 100644 --- a/pkg/repo/v1/index_test.go +++ b/pkg/repo/v1/index_test.go @@ -20,6 +20,7 @@ import ( "bufio" "bytes" "encoding/json" + "errors" "fmt" "net/http" "os" @@ -639,7 +640,7 @@ func TestIgnoreSkippableChartValidationError(t *testing.T) { return } - if tc.Input != result { + if !errors.Is(tc.Input, result) { t.Error("expected the result equal to input") } diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go index f7c29033c..a1847e199 100644 --- a/pkg/storage/driver/sql_test.go +++ b/pkg/storage/driver/sql_test.go @@ -15,6 +15,7 @@ package driver import ( "database/sql/driver" + "errors" "fmt" "reflect" "regexp" @@ -447,7 +448,7 @@ func TestSqlQuery(t *testing.T) { _, err := sqlDriver.Query(labelSetUnknown) if err == nil { t.Errorf("Expected error {%v}, got nil", ErrReleaseNotFound) - } else if err != ErrReleaseNotFound { + } else if !errors.Is(err, ErrReleaseNotFound) { t.Fatalf("failed to query for unknown smug-pigeon release: %v", err) } From 4330bdea0409f428e75145f15532bfa0e2bc945c Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 9 Feb 2026 07:45:25 +0100 Subject: [PATCH 173/571] fix(pkg): errorlint linter #### Description errorlint linter in pkg Signed-off-by: Matthieu MOREL --- pkg/chart/common/capabilities.go | 2 +- pkg/cmd/template.go | 2 +- pkg/downloader/chart_downloader.go | 2 +- pkg/strvals/literal_parser.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/chart/common/capabilities.go b/pkg/chart/common/capabilities.go index 18d00de90..48d6e993f 100644 --- a/pkg/chart/common/capabilities.go +++ b/pkg/chart/common/capabilities.go @@ -157,7 +157,7 @@ func makeDefaultCapabilities() (*Capabilities, error) { v, err := semver.NewVersion(vstr) if err != nil { - return nil, fmt.Errorf("unable to parse k8s.io/client-go version %q: %v", vstr, err) + return nil, fmt.Errorf("unable to parse k8s.io/client-go version %q: %w", vstr, err) } kubeVersionMajor := v.Major() + 1 diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index 14f85042b..047fd60df 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -80,7 +80,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if kubeVersion != "" { parsedKubeVersion, err := common.ParseKubeVersion(kubeVersion) if err != nil { - return fmt.Errorf("invalid kube version '%s': %s", kubeVersion, err) + return fmt.Errorf("invalid kube version '%s': %w", kubeVersion, err) } client.KubeVersion = parsedKubeVersion } diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index ee4f8abe3..67e938448 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -380,7 +380,7 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (string, *url if err != nil { // If there is no special config, return the default HTTP client and // swallow the error. - if err == ErrNoOwnerRepo { + if errors.Is(err, ErrNoOwnerRepo) { // Make sure to add the ref URL as the URL for the getter c.Options = append(c.Options, getter.WithURL(ref)) return "", u, nil diff --git a/pkg/strvals/literal_parser.go b/pkg/strvals/literal_parser.go index d5d4c25b4..c2a824220 100644 --- a/pkg/strvals/literal_parser.go +++ b/pkg/strvals/literal_parser.go @@ -106,7 +106,7 @@ func (t *literalParser) key(data map[string]interface{}, nestedNameLevel int) (r case lastRune == '=': // found end of key: swallow the '=' and get the value value, err := t.val() - if err == nil && err != io.EOF { + if err == nil && !errors.Is(err, io.EOF) { return err } set(data, string(key), string(value)) From abecafa0f507a69888877b9ddb714095714b64c8 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 9 Feb 2026 07:45:31 +0100 Subject: [PATCH 174/571] fix(internal): errorlint linter #### Description errorlint linter in internal Signed-off-by: Matthieu MOREL --- internal/chart/v3/lint/rules/chartfile.go | 2 +- internal/chart/v3/loader/archive.go | 2 +- internal/chart/v3/metadata_test.go | 3 ++- internal/plugin/installer/local_installer_test.go | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/chart/v3/lint/rules/chartfile.go b/internal/chart/v3/lint/rules/chartfile.go index f171b56b9..29991a8d5 100644 --- a/internal/chart/v3/lint/rules/chartfile.go +++ b/internal/chart/v3/lint/rules/chartfile.go @@ -152,7 +152,7 @@ func validateChartVersion(cf *chart.Metadata) error { valid, msg := c.Validate(version) if !valid && len(msg) > 0 { - return fmt.Errorf("version %v", msg[0]) + return fmt.Errorf("version %w", msg[0]) } return nil diff --git a/internal/chart/v3/loader/archive.go b/internal/chart/v3/loader/archive.go index a9d4faf8f..442b8871d 100644 --- a/internal/chart/v3/loader/archive.go +++ b/internal/chart/v3/loader/archive.go @@ -57,7 +57,7 @@ func LoadFile(name string) (*chart.Chart, error) { c, err := LoadArchive(raw) if err != nil { if errors.Is(err, gzip.ErrHeader) { - return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %s)", name, err) + return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %w)", name, err) } } return c, err diff --git a/internal/chart/v3/metadata_test.go b/internal/chart/v3/metadata_test.go index 596a03695..3d773e7f4 100644 --- a/internal/chart/v3/metadata_test.go +++ b/internal/chart/v3/metadata_test.go @@ -16,6 +16,7 @@ limitations under the License. package v3 import ( + "errors" "testing" ) @@ -181,7 +182,7 @@ func TestValidate(t *testing.T) { for _, tt := range tests { result := tt.md.Validate() - if result != tt.err { + if !errors.Is(result, tt.err) { t.Errorf("expected %q, got %q in test %q", tt.err, result, tt.name) } } diff --git a/internal/plugin/installer/local_installer_test.go b/internal/plugin/installer/local_installer_test.go index 3ee8ab6d0..510c2880b 100644 --- a/internal/plugin/installer/local_installer_test.go +++ b/internal/plugin/installer/local_installer_test.go @@ -19,6 +19,7 @@ import ( "archive/tar" "bytes" "compress/gzip" + "errors" "os" "path/filepath" "testing" @@ -64,7 +65,7 @@ func TestLocalInstallerNotAFolder(t *testing.T) { if err == nil { t.Fatal("expected error") } - if err != ErrPluginNotADirectory { + if !errors.Is(err, ErrPluginNotADirectory) { t.Fatalf("expected error to equal: %q", err) } } From 4d0ae7f33a09093f8f52d02b952e3822c87b8c5f Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 9 Feb 2026 07:45:34 +0100 Subject: [PATCH 175/571] fix(pkg): errorlint linter #### Description errorlint linter in pkg Signed-off-by: Matthieu MOREL --- pkg/action/upgrade.go | 4 ++-- pkg/chart/v2/metadata_test.go | 3 ++- pkg/engine/engine.go | 4 ++-- pkg/kube/wait.go | 4 +++- pkg/storage/driver/secrets_test.go | 4 ++-- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 4c93855b1..4b46b54f7 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -455,7 +455,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele if !u.DisableHooks { if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.WaitStrategy, u.WaitOptions, u.Timeout, serverSideApply); err != nil { - u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %s", err)) + u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %w", err)) return } } else { @@ -503,7 +503,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele // post-upgrade hooks if !u.DisableHooks { if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.WaitStrategy, u.WaitOptions, u.Timeout, serverSideApply); err != nil { - u.reportToPerformUpgrade(c, upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err)) + u.reportToPerformUpgrade(c, upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %w", err)) return } } diff --git a/pkg/chart/v2/metadata_test.go b/pkg/chart/v2/metadata_test.go index 7892f0209..2f092f6d2 100644 --- a/pkg/chart/v2/metadata_test.go +++ b/pkg/chart/v2/metadata_test.go @@ -16,6 +16,7 @@ limitations under the License. package v2 import ( + "errors" "testing" ) @@ -181,7 +182,7 @@ func TestValidate(t *testing.T) { for _, tt := range tests { result := tt.md.Validate() - if result != tt.err { + if !errors.Is(result, tt.err) { t.Errorf("expected %q, got %q in test %q", tt.err, result, tt.name) } } diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index f5db7e158..7e50e71a1 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -319,7 +319,7 @@ func cleanupParseError(filename string, err error) error { tokens := strings.Split(err.Error(), ": ") if len(tokens) == 1 { // This might happen if a non-templating error occurs - return fmt.Errorf("parse error in (%s): %s", filename, err) + return fmt.Errorf("parse error in (%s): %w", filename, err) } // The first token is "template" // The second token is either "filename:lineno" or "filename:lineNo:columnNo" @@ -466,7 +466,7 @@ func reformatExecErrorMsg(filename string, err error) error { tokens := strings.SplitN(err.Error(), ": ", 3) if len(tokens) != 3 { // This might happen if a non-templating error occurs - return fmt.Errorf("execution error in (%s): %s", filename, err) + return fmt.Errorf("execution error in (%s): %w", filename, err) } // The first token is "template" diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go index 9a276a459..8b4b2ed1b 100644 --- a/pkg/kube/wait.go +++ b/pkg/kube/wait.go @@ -18,6 +18,7 @@ package kube // import "helm.sh/helm/v4/pkg/kube" import ( "context" + "errors" "fmt" "log/slog" "net/http" @@ -107,7 +108,8 @@ func (hw *legacyWaiter) isRetryableError(err error, resource *resource.Info) boo slog.String("resource", resource.Name), slog.Any("error", err), ) - if ev, ok := err.(*apierrors.StatusError); ok { + ev := &apierrors.StatusError{} + if errors.As(err, &ev) { statusCode := ev.Status().Code retryable := hw.isRetryableHTTPStatusCode(statusCode) slog.Debug( diff --git a/pkg/storage/driver/secrets_test.go b/pkg/storage/driver/secrets_test.go index f4aa1176c..a11ec4380 100644 --- a/pkg/storage/driver/secrets_test.go +++ b/pkg/storage/driver/secrets_test.go @@ -165,7 +165,7 @@ func TestSecretQuery(t *testing.T) { } _, err = secrets.Query(map[string]string{"name": "notExist"}) - if err != ErrReleaseNotFound { + if !errors.Is(err, ErrReleaseNotFound) { t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) } } @@ -237,7 +237,7 @@ func TestSecretDelete(t *testing.T) { // perform the delete on a non-existing release _, err := secrets.Delete("nonexistent") - if err != ErrReleaseNotFound { + if !errors.Is(err, ErrReleaseNotFound) { t.Fatalf("Expected ErrReleaseNotFound, got: {%v}", err) } From 6d1490ed1ea5968235087658d03bb440e4014a36 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 9 Feb 2026 07:45:41 +0100 Subject: [PATCH 176/571] fix(pkg): errorlint linter #### Description errorlint linter in pkg/cmd Signed-off-by: Matthieu MOREL --- pkg/chart/loader/archive/archive.go | 2 +- pkg/chart/v2/lint/rules/chartfile.go | 2 +- pkg/cmd/rollback.go | 2 +- pkg/cmd/search_hub.go | 2 +- pkg/cmd/search_repo.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/chart/loader/archive/archive.go b/pkg/chart/loader/archive/archive.go index e98f5c333..a35c0152d 100644 --- a/pkg/chart/loader/archive/archive.go +++ b/pkg/chart/loader/archive/archive.go @@ -172,7 +172,7 @@ func EnsureArchive(name string, raw *os.File) error { buffer := make([]byte, 512) _, err := raw.Read(buffer) if err != nil && err != io.EOF { - return fmt.Errorf("file '%s' cannot be read: %s", name, err) + return fmt.Errorf("file '%s' cannot be read: %w", name, err) } // Helm may identify achieve of the application/x-gzip as application/vnd.ms-fontobject. diff --git a/pkg/chart/v2/lint/rules/chartfile.go b/pkg/chart/v2/lint/rules/chartfile.go index 806363477..559ccc7c8 100644 --- a/pkg/chart/v2/lint/rules/chartfile.go +++ b/pkg/chart/v2/lint/rules/chartfile.go @@ -153,7 +153,7 @@ func validateChartVersion(cf *chart.Metadata) error { valid, msg := c.Validate(version) if !valid && len(msg) > 0 { - return fmt.Errorf("version %v", msg[0]) + return fmt.Errorf("version %w", msg[0]) } return nil diff --git a/pkg/cmd/rollback.go b/pkg/cmd/rollback.go index 00a2725bc..2cabbb8ef 100644 --- a/pkg/cmd/rollback.go +++ b/pkg/cmd/rollback.go @@ -61,7 +61,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if len(args) > 1 { ver, err := strconv.Atoi(args[1]) if err != nil { - return fmt.Errorf("could not convert revision to a number: %v", err) + return fmt.Errorf("could not convert revision to a number: %w", err) } client.Version = ver } diff --git a/pkg/cmd/search_hub.go b/pkg/cmd/search_hub.go index bb2ff6038..1fde69394 100644 --- a/pkg/cmd/search_hub.go +++ b/pkg/cmd/search_hub.go @@ -141,7 +141,7 @@ func (h *hubSearchWriter) WriteTable(out io.Writer) error { _, err := out.Write([]byte("No results found\n")) if err != nil { - return fmt.Errorf("unable to write results: %s", err) + return fmt.Errorf("unable to write results: %w", err) } return nil } diff --git a/pkg/cmd/search_repo.go b/pkg/cmd/search_repo.go index febb138e2..68c7543ab 100644 --- a/pkg/cmd/search_repo.go +++ b/pkg/cmd/search_repo.go @@ -221,7 +221,7 @@ func (r *repoSearchWriter) WriteTable(out io.Writer) error { _, err := out.Write([]byte("No results found\n")) if err != nil { - return fmt.Errorf("unable to write results: %s", err) + return fmt.Errorf("unable to write results: %w", err) } return nil } From 025418291a7911441e7962895ba4bc24b72b55b3 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 9 Feb 2026 07:45:28 +0100 Subject: [PATCH 177/571] fix(internal): errorlint linter errorlint linter in internal/third_party/dep/fs Signed-off-by: Matthieu MOREL --- internal/chart/v3/util/dependencies.go | 4 +++- internal/plugin/runtime_subprocess_test.go | 4 +++- internal/third_party/dep/fs/fs.go | 3 ++- internal/third_party/dep/fs/fs_test.go | 5 +++-- internal/third_party/dep/fs/rename.go | 6 ++++-- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/internal/chart/v3/util/dependencies.go b/internal/chart/v3/util/dependencies.go index 5bf8a593c..9c4d8e80f 100644 --- a/internal/chart/v3/util/dependencies.go +++ b/internal/chart/v3/util/dependencies.go @@ -16,6 +16,7 @@ limitations under the License. package util import ( + "errors" "fmt" "log/slog" "strings" @@ -44,6 +45,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values, if len(c) > 0 { // retrieve value vv, err := cvals.PathValue(cpath + c) + var errNoValue common.ErrNoValue if err == nil { // if not bool, warn if bv, ok := vv.(bool); ok { @@ -51,7 +53,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values, break } slog.Warn("returned non-bool value", "path", c, "chart", r.Name) - } else if _, ok := err.(common.ErrNoValue); !ok { + } else if errors.As(err, &errNoValue) { // this is a real error slog.Warn("the method PathValue returned error", slog.Any("error", err)) } diff --git a/internal/plugin/runtime_subprocess_test.go b/internal/plugin/runtime_subprocess_test.go index ed251d28b..271e79661 100644 --- a/internal/plugin/runtime_subprocess_test.go +++ b/internal/plugin/runtime_subprocess_test.go @@ -16,6 +16,7 @@ limitations under the License. package plugin import ( + "errors" "fmt" "os" "path/filepath" @@ -76,7 +77,8 @@ func TestSubprocessPluginRuntime(t *testing.T) { }) require.Error(t, err) - ieerr, ok := err.(*InvokeExecError) + ieerr := &InvokeExecError{} + ok := errors.As(err, &ieerr) require.True(t, ok, "expected InvokeExecError, got %T", err) assert.Equal(t, 56, ieerr.ExitCode) diff --git a/internal/third_party/dep/fs/fs.go b/internal/third_party/dep/fs/fs.go index 6e2720f3b..3140cf91e 100644 --- a/internal/third_party/dep/fs/fs.go +++ b/internal/third_party/dep/fs/fs.go @@ -164,7 +164,8 @@ func CopyFile(src, dst string) (err error) { // // ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522): // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx - if lerr, ok := err.(*os.LinkError); ok && lerr.Err != syscall.Errno(1314) { + lerr := &os.LinkError{} + if errors.As(err, &lerr) && !errors.Is(lerr.Err, syscall.Errno(1314)) { return err } } else { diff --git a/internal/third_party/dep/fs/fs_test.go b/internal/third_party/dep/fs/fs_test.go index 610771bc3..8f28c3af7 100644 --- a/internal/third_party/dep/fs/fs_test.go +++ b/internal/third_party/dep/fs/fs_test.go @@ -32,6 +32,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package fs import ( + "errors" "os" "path/filepath" "runtime" @@ -234,7 +235,7 @@ func TestCopyDirFail_SrcIsNotDir(t *testing.T) { t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir) } - if err != errSrcNotDir { + if !errors.Is(err, errSrcNotDir) { t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errSrcNotDir, srcdir, dstdir, err) } @@ -260,7 +261,7 @@ func TestCopyDirFail_DstExists(t *testing.T) { t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir) } - if err != errDstExist { + if !errors.Is(err, errDstExist) { t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errDstExist, srcdir, dstdir, err) } } diff --git a/internal/third_party/dep/fs/rename.go b/internal/third_party/dep/fs/rename.go index 5f13b1ca3..77c93b7ad 100644 --- a/internal/third_party/dep/fs/rename.go +++ b/internal/third_party/dep/fs/rename.go @@ -34,6 +34,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package fs import ( + "errors" "fmt" "os" "syscall" @@ -46,10 +47,11 @@ func renameFallback(err error, src, dst string) error { // copy if we detect that case. syscall.EXDEV is the common name for the // cross device link error which has varying output text across different // operating systems. - terr, ok := err.(*os.LinkError) + terr := &os.LinkError{} + ok := errors.As(err, &terr) if !ok { return err - } else if terr.Err != syscall.EXDEV { + } else if !errors.Is(terr.Err, syscall.EXDEV) { return fmt.Errorf("link error: cannot rename %s to %s: %w", src, dst, terr) } From 259f76a849391e6ff60a9a2e95ce7310d958c602 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 9 Feb 2026 07:45:18 +0100 Subject: [PATCH 178/571] fix(pkg): errorlint linter errorlint linter in pkg/cmd Signed-off-by: Matthieu MOREL --- pkg/action/install.go | 4 ++-- pkg/chart/v2/util/dependencies.go | 4 +++- pkg/cmd/dependency_build.go | 4 +++- pkg/cmd/load_plugins.go | 4 +++- pkg/storage/driver/cfgmaps_test.go | 4 ++-- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index 0fe1f1a6e..7857b9035 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -488,7 +488,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource // pre-install hooks if !i.DisableHooks { if err := i.cfg.execHook(rel, release.HookPreInstall, i.WaitStrategy, i.WaitOptions, i.Timeout, i.ServerSideApply); err != nil { - return rel, fmt.Errorf("failed pre-install: %s", err) + return rel, fmt.Errorf("failed pre-install: %w", err) } } @@ -534,7 +534,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource if !i.DisableHooks { if err := i.cfg.execHook(rel, release.HookPostInstall, i.WaitStrategy, i.WaitOptions, i.Timeout, i.ServerSideApply); err != nil { - return rel, fmt.Errorf("failed post-install: %s", err) + return rel, fmt.Errorf("failed post-install: %w", err) } } diff --git a/pkg/chart/v2/util/dependencies.go b/pkg/chart/v2/util/dependencies.go index c7bb6621e..aa242e0ca 100644 --- a/pkg/chart/v2/util/dependencies.go +++ b/pkg/chart/v2/util/dependencies.go @@ -16,6 +16,7 @@ limitations under the License. package util import ( + "errors" "fmt" "log/slog" "strings" @@ -44,6 +45,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values, if len(c) > 0 { // retrieve value vv, err := cvals.PathValue(cpath + c) + var errNoValue common.ErrNoValue if err == nil { // if not bool, warn if bv, ok := vv.(bool); ok { @@ -51,7 +53,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values, break } slog.Warn("returned non-bool value", "path", c, "chart", r.Name) - } else if _, ok := err.(common.ErrNoValue); !ok { + } else if !errors.As(err, &errNoValue) { // this is a real error slog.Warn("the method PathValue returned error", slog.Any("error", err)) } diff --git a/pkg/cmd/dependency_build.go b/pkg/cmd/dependency_build.go index 7e5c731b7..b8ac16e60 100644 --- a/pkg/cmd/dependency_build.go +++ b/pkg/cmd/dependency_build.go @@ -16,6 +16,7 @@ limitations under the License. package cmd import ( + "errors" "fmt" "io" "os" @@ -76,7 +77,8 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command { man.Verify = downloader.VerifyIfPossible } err = man.Build() - if e, ok := err.(downloader.ErrRepoNotFound); ok { + var e downloader.ErrRepoNotFound + if errors.As(err, &e) { return fmt.Errorf("%s. Please add the missing repos via 'helm repo add'", e.Error()) } return err diff --git a/pkg/cmd/load_plugins.go b/pkg/cmd/load_plugins.go index 7bdbf597d..029dd04f5 100644 --- a/pkg/cmd/load_plugins.go +++ b/pkg/cmd/load_plugins.go @@ -18,6 +18,7 @@ package cmd import ( "bytes" "context" + "errors" "fmt" "io" "log/slog" @@ -120,7 +121,8 @@ func loadCLIPlugins(baseCmd *cobra.Command, out io.Writer) { Stderr: os.Stderr, } _, err = plug.Invoke(context.Background(), input) - if execErr, ok := err.(*plugin.InvokeExecError); ok { + execErr := &plugin.InvokeExecError{} + if errors.As(err, &execErr) { return CommandError{ error: execErr.Err, ExitCode: execErr.ExitCode, diff --git a/pkg/storage/driver/cfgmaps_test.go b/pkg/storage/driver/cfgmaps_test.go index 8beb45547..947ebff71 100644 --- a/pkg/storage/driver/cfgmaps_test.go +++ b/pkg/storage/driver/cfgmaps_test.go @@ -180,7 +180,7 @@ func TestConfigMapQuery(t *testing.T) { } _, err = cfgmaps.Query(map[string]string{"name": "notExist"}) - if err != ErrReleaseNotFound { + if !errors.Is(err, ErrReleaseNotFound) { t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) } } @@ -252,7 +252,7 @@ func TestConfigMapDelete(t *testing.T) { // perform the delete on a non-existent release _, err := cfgmaps.Delete("nonexistent") - if err != ErrReleaseNotFound { + if !errors.Is(err, ErrReleaseNotFound) { t.Fatalf("Expected ErrReleaseNotFound: got {%v}", err) } From 2e266c3ec9d70a6e656f8041bb31475e25e9eb22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 00:15:18 +0000 Subject: [PATCH 179/571] chore(deps): bump sigs.k8s.io/kustomize/kyaml from 0.21.0 to 0.21.1 Bumps [sigs.k8s.io/kustomize/kyaml](https://github.com/kubernetes-sigs/kustomize) from 0.21.0 to 0.21.1. - [Release notes](https://github.com/kubernetes-sigs/kustomize/releases) - [Commits](https://github.com/kubernetes-sigs/kustomize/compare/api/v0.21.0...api/v0.21.1) --- updated-dependencies: - dependency-name: sigs.k8s.io/kustomize/kyaml dependency-version: 0.21.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 92de0e40f..d4c52b192 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,7 @@ require ( k8s.io/kubectl v0.35.0 oras.land/oras-go/v2 v2.6.0 sigs.k8s.io/controller-runtime v0.23.1 - sigs.k8s.io/kustomize/kyaml v0.21.0 + sigs.k8s.io/kustomize/kyaml v0.21.1 sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index 46e86d3e3..c423bb221 100644 --- a/go.sum +++ b/go.sum @@ -509,8 +509,8 @@ sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5E sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= -sigs.k8s.io/kustomize/kyaml v0.21.0 h1:7mQAf3dUwf0wBerWJd8rXhVcnkk5Tvn/q91cGkaP6HQ= -sigs.k8s.io/kustomize/kyaml v0.21.0/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= +sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= +sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs= From 5cddc95bed0572b8d63a910843f0a70477a4ce33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 00:15:29 +0000 Subject: [PATCH 180/571] chore(deps): bump golang.org/x/text from 0.33.0 to 0.34.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.33.0 to 0.34.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.33.0...v0.34.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.34.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 92de0e40f..d3b24bb9a 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.47.0 golang.org/x/term v0.39.0 - golang.org/x/text v0.33.0 + golang.org/x/text v0.34.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.35.0 k8s.io/apiextensions-apiserver v0.35.0 @@ -157,13 +157,13 @@ require ( go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/mod v0.31.0 // indirect - golang.org/x/net v0.48.0 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.40.0 // indirect + golang.org/x/tools v0.41.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect google.golang.org/grpc v1.72.2 // indirect diff --git a/go.sum b/go.sum index 46e86d3e3..45f429f8d 100644 --- a/go.sum +++ b/go.sum @@ -383,8 +383,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= -golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -395,8 +395,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -444,8 +444,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -454,8 +454,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= From a23b6388ac74984460fd4055de5120d2fc03d841 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 21:34:40 +0000 Subject: [PATCH 181/571] chore(deps): bump github.com/lib/pq from 1.11.1 to 1.11.2 Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.11.1 to 1.11.2. - [Release notes](https://github.com/lib/pq/releases) - [Changelog](https://github.com/lib/pq/blob/master/CHANGELOG.md) - [Commits](https://github.com/lib/pq/compare/v1.11.1...v1.11.2) --- updated-dependencies: - dependency-name: github.com/lib/pq dependency-version: 1.11.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 92de0e40f..d03530bba 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/gofrs/flock v0.13.0 github.com/gosuri/uitable v0.0.4 github.com/jmoiron/sqlx v1.4.0 - github.com/lib/pq v1.11.1 + github.com/lib/pq v1.11.2 github.com/mattn/go-shellwords v1.0.12 github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 diff --git a/go.sum b/go.sum index 46e86d3e3..b00569302 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI= -github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= +github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= From aec7aced38d053a2df5d0973abdf21848778a722 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 05:09:52 +0000 Subject: [PATCH 182/571] chore(deps): bump golang.org/x/term from 0.39.0 to 0.40.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.39.0 to 0.40.0. - [Commits](https://github.com/golang/term/compare/v0.39.0...v0.40.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-version: 0.40.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 4aaeb932a..ac9bce44a 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/tetratelabs/wazero v1.11.0 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.47.0 - golang.org/x/term v0.39.0 + golang.org/x/term v0.40.0 golang.org/x/text v0.34.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.35.0 @@ -161,7 +161,7 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect + golang.org/x/sys v0.41.0 // indirect golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.41.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect diff --git a/go.sum b/go.sum index 9b22a40c5..eba8fe939 100644 --- a/go.sum +++ b/go.sum @@ -426,8 +426,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -435,8 +435,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= From 782385397ce1871f4c8a4d2e3c857937bd8988c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 05:32:54 +0000 Subject: [PATCH 183/571] chore(deps): bump golang.org/x/crypto from 0.47.0 to 0.48.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.47.0 to 0.48.0. - [Commits](https://github.com/golang/crypto/compare/v0.47.0...v0.48.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.48.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ac9bce44a..2a7787076 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/tetratelabs/wazero v1.11.0 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.47.0 + golang.org/x/crypto v0.48.0 golang.org/x/term v0.40.0 golang.org/x/text v0.34.0 gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index eba8fe939..35b74a320 100644 --- a/go.sum +++ b/go.sum @@ -377,8 +377,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= From 4a91f3ad5cc0c1521f6d4dcb5681e2da4baaa157 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Fri, 13 Feb 2026 19:10:56 +0000 Subject: [PATCH 184/571] feat(release): add internal/release/v2 package for chart v3 support (#31709) Introduce release/v2 that mirrors pkg/release/v1 but uses *v3.Chart from internal/chart/v3. The code is structurally identical to v1 with only import paths changed to reference internal/chart/v3 instead of pkg/chart/v2. - Add internal/release/v2 with Release, Info, Hook types - Add internal/release/v2/util with filter, sorter, manifest utilities - Update pkg/release/common.go with v2Accessor and v2HookAccessor - Copy all test files from pkg/release/v1 and add a v2 test in common_test.go Signed-off-by: Evans Mungai --- internal/release/v2/doc.go | 17 + internal/release/v2/hook.go | 189 ++++++++++ internal/release/v2/hook_test.go | 231 ++++++++++++ internal/release/v2/info.go | 125 +++++++ internal/release/v2/info_test.go | 285 ++++++++++++++ internal/release/v2/mock.go | 143 ++++++++ internal/release/v2/release.go | 60 +++ internal/release/v2/util/filter.go | 81 ++++ internal/release/v2/util/filter_test.go | 60 +++ internal/release/v2/util/kind_sorter.go | 165 +++++++++ internal/release/v2/util/kind_sorter_test.go | 347 ++++++++++++++++++ internal/release/v2/util/manifest.go | 72 ++++ internal/release/v2/util/manifest_sorter.go | 244 ++++++++++++ .../release/v2/util/manifest_sorter_test.go | 227 ++++++++++++ internal/release/v2/util/manifest_test.go | 61 +++ internal/release/v2/util/sorter.go | 61 +++ internal/release/v2/util/sorter_test.go | 109 ++++++ pkg/release/common.go | 73 ++++ pkg/release/common_test.go | 70 ++++ 19 files changed, 2620 insertions(+) create mode 100644 internal/release/v2/doc.go create mode 100644 internal/release/v2/hook.go create mode 100644 internal/release/v2/hook_test.go create mode 100644 internal/release/v2/info.go create mode 100644 internal/release/v2/info_test.go create mode 100644 internal/release/v2/mock.go create mode 100644 internal/release/v2/release.go create mode 100644 internal/release/v2/util/filter.go create mode 100644 internal/release/v2/util/filter_test.go create mode 100644 internal/release/v2/util/kind_sorter.go create mode 100644 internal/release/v2/util/kind_sorter_test.go create mode 100644 internal/release/v2/util/manifest.go create mode 100644 internal/release/v2/util/manifest_sorter.go create mode 100644 internal/release/v2/util/manifest_sorter_test.go create mode 100644 internal/release/v2/util/manifest_test.go create mode 100644 internal/release/v2/util/sorter.go create mode 100644 internal/release/v2/util/sorter_test.go diff --git a/internal/release/v2/doc.go b/internal/release/v2/doc.go new file mode 100644 index 000000000..5b8cebee3 --- /dev/null +++ b/internal/release/v2/doc.go @@ -0,0 +1,17 @@ +/* +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 v2 provides release handling for apiVersion v3 charts. +package v2 diff --git a/internal/release/v2/hook.go b/internal/release/v2/hook.go new file mode 100644 index 000000000..255b6ed01 --- /dev/null +++ b/internal/release/v2/hook.go @@ -0,0 +1,189 @@ +/* +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 v2 + +import ( + "encoding/json" + "time" +) + +// HookEvent specifies the hook event +type HookEvent string + +// Hook event types +const ( + HookPreInstall HookEvent = "pre-install" + HookPostInstall HookEvent = "post-install" + HookPreDelete HookEvent = "pre-delete" + HookPostDelete HookEvent = "post-delete" + HookPreUpgrade HookEvent = "pre-upgrade" + HookPostUpgrade HookEvent = "post-upgrade" + HookPreRollback HookEvent = "pre-rollback" + HookPostRollback HookEvent = "post-rollback" + HookTest HookEvent = "test" +) + +func (x HookEvent) String() string { return string(x) } + +// HookDeletePolicy specifies the hook delete policy +type HookDeletePolicy string + +// Hook delete policy types +const ( + HookSucceeded HookDeletePolicy = "hook-succeeded" + HookFailed HookDeletePolicy = "hook-failed" + HookBeforeHookCreation HookDeletePolicy = "before-hook-creation" +) + +func (x HookDeletePolicy) String() string { return string(x) } + +// HookOutputLogPolicy specifies the hook output log policy +type HookOutputLogPolicy string + +// Hook output log policy types +const ( + HookOutputOnSucceeded HookOutputLogPolicy = "hook-succeeded" + HookOutputOnFailed HookOutputLogPolicy = "hook-failed" +) + +func (x HookOutputLogPolicy) String() string { return string(x) } + +// HookAnnotation is the label name for a hook +const HookAnnotation = "helm.sh/hook" + +// HookWeightAnnotation is the label name for a hook weight +const HookWeightAnnotation = "helm.sh/hook-weight" + +// HookDeleteAnnotation is the label name for the delete policy for a hook +const HookDeleteAnnotation = "helm.sh/hook-delete-policy" + +// HookOutputLogAnnotation is the label name for the output log policy for a hook +const HookOutputLogAnnotation = "helm.sh/hook-output-log-policy" + +// Hook defines a hook object. +type Hook struct { + Name string `json:"name,omitempty"` + // Kind is the Kubernetes kind. + Kind string `json:"kind,omitempty"` + // Path is the chart-relative path to the template. + Path string `json:"path,omitempty"` + // Manifest is the manifest contents. + Manifest string `json:"manifest,omitempty"` + // Events are the events that this hook fires on. + Events []HookEvent `json:"events,omitempty"` + // LastRun indicates the date/time this was last run. + LastRun HookExecution `json:"last_run,omitempty"` + // Weight indicates the sort order for execution among similar Hook type + Weight int `json:"weight,omitempty"` + // DeletePolicies are the policies that indicate when to delete the hook + DeletePolicies []HookDeletePolicy `json:"delete_policies,omitempty"` + // OutputLogPolicies defines whether we should copy hook logs back to main process + OutputLogPolicies []HookOutputLogPolicy `json:"output_log_policies,omitempty"` +} + +// A HookExecution records the result for the last execution of a hook for a given release. +type HookExecution struct { + // StartedAt indicates the date/time this hook was started + StartedAt time.Time `json:"started_at,omitzero"` + // CompletedAt indicates the date/time this hook was completed. + CompletedAt time.Time `json:"completed_at,omitzero"` + // Phase indicates whether the hook completed successfully + Phase HookPhase `json:"phase"` +} + +// A HookPhase indicates the state of a hook execution +type HookPhase string + +const ( + // HookPhaseUnknown indicates that a hook is in an unknown state + HookPhaseUnknown HookPhase = "Unknown" + // HookPhaseRunning indicates that a hook is currently executing + HookPhaseRunning HookPhase = "Running" + // HookPhaseSucceeded indicates that hook execution succeeded + HookPhaseSucceeded HookPhase = "Succeeded" + // HookPhaseFailed indicates that hook execution failed + HookPhaseFailed HookPhase = "Failed" +) + +// String converts a hook phase to a printable string +func (x HookPhase) String() string { return string(x) } + +// hookExecutionJSON is used for custom JSON marshaling/unmarshaling +type hookExecutionJSON struct { + StartedAt *time.Time `json:"started_at,omitempty"` + CompletedAt *time.Time `json:"completed_at,omitempty"` + Phase HookPhase `json:"phase"` +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// It handles empty string time fields by treating them as zero values. +func (h *HookExecution) UnmarshalJSON(data []byte) error { + // First try to unmarshal into a map to handle empty string time fields + var raw map[string]interface{} + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + // Replace empty string time fields with nil + for _, field := range []string{"started_at", "completed_at"} { + if val, ok := raw[field]; ok { + if str, ok := val.(string); ok && str == "" { + raw[field] = nil + } + } + } + + // Re-marshal with cleaned data + cleaned, err := json.Marshal(raw) + if err != nil { + return err + } + + // Unmarshal into temporary struct with pointer time fields + var tmp hookExecutionJSON + if err := json.Unmarshal(cleaned, &tmp); err != nil { + return err + } + + // Copy values to HookExecution struct + if tmp.StartedAt != nil { + h.StartedAt = *tmp.StartedAt + } + if tmp.CompletedAt != nil { + h.CompletedAt = *tmp.CompletedAt + } + h.Phase = tmp.Phase + + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +// It omits zero-value time fields from the JSON output. +func (h HookExecution) MarshalJSON() ([]byte, error) { + tmp := hookExecutionJSON{ + Phase: h.Phase, + } + + if !h.StartedAt.IsZero() { + tmp.StartedAt = &h.StartedAt + } + if !h.CompletedAt.IsZero() { + tmp.CompletedAt = &h.CompletedAt + } + + return json.Marshal(tmp) +} diff --git a/internal/release/v2/hook_test.go b/internal/release/v2/hook_test.go new file mode 100644 index 000000000..c3e2e0261 --- /dev/null +++ b/internal/release/v2/hook_test.go @@ -0,0 +1,231 @@ +/* +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 v2 + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHookExecutionMarshalJSON(t *testing.T) { + started := time.Date(2025, 10, 8, 12, 0, 0, 0, time.UTC) + completed := time.Date(2025, 10, 8, 12, 5, 0, 0, time.UTC) + + tests := []struct { + name string + exec HookExecution + expected string + }{ + { + name: "all fields populated", + exec: HookExecution{ + StartedAt: started, + CompletedAt: completed, + Phase: HookPhaseSucceeded, + }, + expected: `{"started_at":"2025-10-08T12:00:00Z","completed_at":"2025-10-08T12:05:00Z","phase":"Succeeded"}`, + }, + { + name: "only phase", + exec: HookExecution{ + Phase: HookPhaseRunning, + }, + expected: `{"phase":"Running"}`, + }, + { + name: "with started time only", + exec: HookExecution{ + StartedAt: started, + Phase: HookPhaseRunning, + }, + expected: `{"started_at":"2025-10-08T12:00:00Z","phase":"Running"}`, + }, + { + name: "failed phase", + exec: HookExecution{ + StartedAt: started, + CompletedAt: completed, + Phase: HookPhaseFailed, + }, + expected: `{"started_at":"2025-10-08T12:00:00Z","completed_at":"2025-10-08T12:05:00Z","phase":"Failed"}`, + }, + { + name: "unknown phase", + exec: HookExecution{ + Phase: HookPhaseUnknown, + }, + expected: `{"phase":"Unknown"}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := json.Marshal(&tt.exec) + require.NoError(t, err) + assert.JSONEq(t, tt.expected, string(data)) + }) + } +} + +func TestHookExecutionUnmarshalJSON(t *testing.T) { + started := time.Date(2025, 10, 8, 12, 0, 0, 0, time.UTC) + completed := time.Date(2025, 10, 8, 12, 5, 0, 0, time.UTC) + + tests := []struct { + name string + input string + expected HookExecution + wantErr bool + }{ + { + name: "all fields populated", + input: `{"started_at":"2025-10-08T12:00:00Z","completed_at":"2025-10-08T12:05:00Z","phase":"Succeeded"}`, + expected: HookExecution{ + StartedAt: started, + CompletedAt: completed, + Phase: HookPhaseSucceeded, + }, + }, + { + name: "only phase", + input: `{"phase":"Running"}`, + expected: HookExecution{ + Phase: HookPhaseRunning, + }, + }, + { + name: "empty string time fields", + input: `{"started_at":"","completed_at":"","phase":"Succeeded"}`, + expected: HookExecution{ + Phase: HookPhaseSucceeded, + }, + }, + { + name: "missing time fields", + input: `{"phase":"Failed"}`, + expected: HookExecution{ + Phase: HookPhaseFailed, + }, + }, + { + name: "null time fields", + input: `{"started_at":null,"completed_at":null,"phase":"Unknown"}`, + expected: HookExecution{ + Phase: HookPhaseUnknown, + }, + }, + { + name: "mixed empty and valid time fields", + input: `{"started_at":"2025-10-08T12:00:00Z","completed_at":"","phase":"Running"}`, + expected: HookExecution{ + StartedAt: started, + Phase: HookPhaseRunning, + }, + }, + { + name: "with started time only", + input: `{"started_at":"2025-10-08T12:00:00Z","phase":"Running"}`, + expected: HookExecution{ + StartedAt: started, + Phase: HookPhaseRunning, + }, + }, + { + name: "failed phase with times", + input: `{"started_at":"2025-10-08T12:00:00Z","completed_at":"2025-10-08T12:05:00Z","phase":"Failed"}`, + expected: HookExecution{ + StartedAt: started, + CompletedAt: completed, + Phase: HookPhaseFailed, + }, + }, + { + name: "invalid time format", + input: `{"started_at":"invalid-time","phase":"Running"}`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var exec HookExecution + err := json.Unmarshal([]byte(tt.input), &exec) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.expected.StartedAt.Unix(), exec.StartedAt.Unix()) + assert.Equal(t, tt.expected.CompletedAt.Unix(), exec.CompletedAt.Unix()) + assert.Equal(t, tt.expected.Phase, exec.Phase) + }) + } +} + +func TestHookExecutionRoundTrip(t *testing.T) { + started := time.Date(2025, 10, 8, 12, 0, 0, 0, time.UTC) + completed := time.Date(2025, 10, 8, 12, 5, 0, 0, time.UTC) + + original := HookExecution{ + StartedAt: started, + CompletedAt: completed, + Phase: HookPhaseSucceeded, + } + + data, err := json.Marshal(&original) + require.NoError(t, err) + + var decoded HookExecution + err = json.Unmarshal(data, &decoded) + require.NoError(t, err) + + assert.Equal(t, original.StartedAt.Unix(), decoded.StartedAt.Unix()) + assert.Equal(t, original.CompletedAt.Unix(), decoded.CompletedAt.Unix()) + assert.Equal(t, original.Phase, decoded.Phase) +} + +func TestHookExecutionEmptyStringRoundTrip(t *testing.T) { + // This test specifically verifies that empty string time fields + // are handled correctly during parsing + input := `{"started_at":"","completed_at":"","phase":"Succeeded"}` + + var exec HookExecution + err := json.Unmarshal([]byte(input), &exec) + require.NoError(t, err) + + // Verify time fields are zero values + assert.True(t, exec.StartedAt.IsZero()) + assert.True(t, exec.CompletedAt.IsZero()) + assert.Equal(t, HookPhaseSucceeded, exec.Phase) + + // Marshal back and verify empty time fields are omitted + data, err := json.Marshal(&exec) + require.NoError(t, err) + + var result map[string]interface{} + err = json.Unmarshal(data, &result) + require.NoError(t, err) + + // Zero time values should be omitted + assert.NotContains(t, result, "started_at") + assert.NotContains(t, result, "completed_at") + assert.Equal(t, "Succeeded", result["phase"]) +} diff --git a/internal/release/v2/info.go b/internal/release/v2/info.go new file mode 100644 index 000000000..038f19409 --- /dev/null +++ b/internal/release/v2/info.go @@ -0,0 +1,125 @@ +/* +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 v2 + +import ( + "encoding/json" + "time" + + "helm.sh/helm/v4/pkg/release/common" + + "k8s.io/apimachinery/pkg/runtime" +) + +// Info describes release information. +type Info struct { + // FirstDeployed is when the release was first deployed. + FirstDeployed time.Time `json:"first_deployed,omitzero"` + // LastDeployed is when the release was last deployed. + LastDeployed time.Time `json:"last_deployed,omitzero"` + // Deleted tracks when this object was deleted. + Deleted time.Time `json:"deleted,omitzero"` + // Description is human-friendly "log entry" about this release. + Description string `json:"description,omitempty"` + // Status is the current state of the release + Status common.Status `json:"status,omitempty"` + // Contains the rendered templates/NOTES.txt if available + Notes string `json:"notes,omitempty"` + // Contains the deployed resources information + Resources map[string][]runtime.Object `json:"resources,omitempty"` +} + +// infoJSON is used for custom JSON marshaling/unmarshaling +type infoJSON struct { + FirstDeployed *time.Time `json:"first_deployed,omitempty"` + LastDeployed *time.Time `json:"last_deployed,omitempty"` + Deleted *time.Time `json:"deleted,omitempty"` + Description string `json:"description,omitempty"` + Status common.Status `json:"status,omitempty"` + Notes string `json:"notes,omitempty"` + Resources map[string][]runtime.Object `json:"resources,omitempty"` +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// It handles empty string time fields by treating them as zero values. +func (i *Info) UnmarshalJSON(data []byte) error { + // First try to unmarshal into a map to handle empty string time fields + var raw map[string]interface{} + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + // Replace empty string time fields with nil + for _, field := range []string{"first_deployed", "last_deployed", "deleted"} { + if val, ok := raw[field]; ok { + if str, ok := val.(string); ok && str == "" { + raw[field] = nil + } + } + } + + // Re-marshal with cleaned data + cleaned, err := json.Marshal(raw) + if err != nil { + return err + } + + // Unmarshal into temporary struct with pointer time fields + var tmp infoJSON + if err := json.Unmarshal(cleaned, &tmp); err != nil { + return err + } + + // Copy values to Info struct + if tmp.FirstDeployed != nil { + i.FirstDeployed = *tmp.FirstDeployed + } + if tmp.LastDeployed != nil { + i.LastDeployed = *tmp.LastDeployed + } + if tmp.Deleted != nil { + i.Deleted = *tmp.Deleted + } + i.Description = tmp.Description + i.Status = tmp.Status + i.Notes = tmp.Notes + i.Resources = tmp.Resources + + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +// It omits zero-value time fields from the JSON output. +func (i Info) MarshalJSON() ([]byte, error) { + tmp := infoJSON{ + Description: i.Description, + Status: i.Status, + Notes: i.Notes, + Resources: i.Resources, + } + + if !i.FirstDeployed.IsZero() { + tmp.FirstDeployed = &i.FirstDeployed + } + if !i.LastDeployed.IsZero() { + tmp.LastDeployed = &i.LastDeployed + } + if !i.Deleted.IsZero() { + tmp.Deleted = &i.Deleted + } + + return json.Marshal(tmp) +} diff --git a/internal/release/v2/info_test.go b/internal/release/v2/info_test.go new file mode 100644 index 000000000..32babb82f --- /dev/null +++ b/internal/release/v2/info_test.go @@ -0,0 +1,285 @@ +/* +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 v2 + +import ( + "encoding/json" + "testing" + "time" + + "helm.sh/helm/v4/pkg/release/common" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestInfoMarshalJSON(t *testing.T) { + now := time.Date(2025, 10, 8, 12, 0, 0, 0, time.UTC) + later := time.Date(2025, 10, 8, 13, 0, 0, 0, time.UTC) + deleted := time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC) + + tests := []struct { + name string + info Info + expected string + }{ + { + name: "all fields populated", + info: Info{ + FirstDeployed: now, + LastDeployed: later, + Deleted: deleted, + Description: "Test release", + Status: common.StatusDeployed, + Notes: "Test notes", + }, + expected: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","deleted":"2025-10-08T14:00:00Z","description":"Test release","status":"deployed","notes":"Test notes"}`, + }, + { + name: "only required fields", + info: Info{ + FirstDeployed: now, + LastDeployed: later, + Status: common.StatusDeployed, + }, + expected: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","status":"deployed"}`, + }, + { + name: "zero time values omitted", + info: Info{ + Description: "Test release", + Status: common.StatusDeployed, + }, + expected: `{"description":"Test release","status":"deployed"}`, + }, + { + name: "with pending status", + info: Info{ + FirstDeployed: now, + LastDeployed: later, + Status: common.StatusPendingInstall, + Description: "Installing release", + }, + expected: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","description":"Installing release","status":"pending-install"}`, + }, + { + name: "uninstalled with deleted time", + info: Info{ + FirstDeployed: now, + LastDeployed: later, + Deleted: deleted, + Status: common.StatusUninstalled, + Description: "Uninstalled release", + }, + expected: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","deleted":"2025-10-08T14:00:00Z","description":"Uninstalled release","status":"uninstalled"}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := json.Marshal(&tt.info) + require.NoError(t, err) + assert.JSONEq(t, tt.expected, string(data)) + }) + } +} + +func TestInfoUnmarshalJSON(t *testing.T) { + now := time.Date(2025, 10, 8, 12, 0, 0, 0, time.UTC) + later := time.Date(2025, 10, 8, 13, 0, 0, 0, time.UTC) + deleted := time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC) + + tests := []struct { + name string + input string + expected Info + wantErr bool + }{ + { + name: "all fields populated", + input: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","deleted":"2025-10-08T14:00:00Z","description":"Test release","status":"deployed","notes":"Test notes"}`, + expected: Info{ + FirstDeployed: now, + LastDeployed: later, + Deleted: deleted, + Description: "Test release", + Status: common.StatusDeployed, + Notes: "Test notes", + }, + }, + { + name: "only required fields", + input: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","status":"deployed"}`, + expected: Info{ + FirstDeployed: now, + LastDeployed: later, + Status: common.StatusDeployed, + }, + }, + { + name: "empty string time fields", + input: `{"first_deployed":"","last_deployed":"","deleted":"","description":"Test release","status":"deployed"}`, + expected: Info{ + Description: "Test release", + Status: common.StatusDeployed, + }, + }, + { + name: "missing time fields", + input: `{"description":"Test release","status":"deployed"}`, + expected: Info{ + Description: "Test release", + Status: common.StatusDeployed, + }, + }, + { + name: "null time fields", + input: `{"first_deployed":null,"last_deployed":null,"deleted":null,"description":"Test release","status":"deployed"}`, + expected: Info{ + Description: "Test release", + Status: common.StatusDeployed, + }, + }, + { + name: "mixed empty and valid time fields", + input: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"","deleted":"","status":"deployed"}`, + expected: Info{ + FirstDeployed: now, + Status: common.StatusDeployed, + }, + }, + { + name: "pending install status", + input: `{"first_deployed":"2025-10-08T12:00:00Z","status":"pending-install","description":"Installing"}`, + expected: Info{ + FirstDeployed: now, + Status: common.StatusPendingInstall, + Description: "Installing", + }, + }, + { + name: "uninstalled with deleted time", + input: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","deleted":"2025-10-08T14:00:00Z","status":"uninstalled"}`, + expected: Info{ + FirstDeployed: now, + LastDeployed: later, + Deleted: deleted, + Status: common.StatusUninstalled, + }, + }, + { + name: "failed status", + input: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","status":"failed","description":"Deployment failed"}`, + expected: Info{ + FirstDeployed: now, + LastDeployed: later, + Status: common.StatusFailed, + Description: "Deployment failed", + }, + }, + { + name: "invalid time format", + input: `{"first_deployed":"invalid-time","status":"deployed"}`, + wantErr: true, + }, + { + name: "empty object", + input: `{}`, + expected: Info{ + Status: "", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var info Info + err := json.Unmarshal([]byte(tt.input), &info) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.expected.FirstDeployed.Unix(), info.FirstDeployed.Unix()) + assert.Equal(t, tt.expected.LastDeployed.Unix(), info.LastDeployed.Unix()) + assert.Equal(t, tt.expected.Deleted.Unix(), info.Deleted.Unix()) + assert.Equal(t, tt.expected.Description, info.Description) + assert.Equal(t, tt.expected.Status, info.Status) + assert.Equal(t, tt.expected.Notes, info.Notes) + assert.Equal(t, tt.expected.Resources, info.Resources) + }) + } +} + +func TestInfoRoundTrip(t *testing.T) { + now := time.Date(2025, 10, 8, 12, 0, 0, 0, time.UTC) + later := time.Date(2025, 10, 8, 13, 0, 0, 0, time.UTC) + + original := Info{ + FirstDeployed: now, + LastDeployed: later, + Description: "Test release", + Status: common.StatusDeployed, + Notes: "Release notes", + } + + data, err := json.Marshal(&original) + require.NoError(t, err) + + var decoded Info + err = json.Unmarshal(data, &decoded) + require.NoError(t, err) + + assert.Equal(t, original.FirstDeployed.Unix(), decoded.FirstDeployed.Unix()) + assert.Equal(t, original.LastDeployed.Unix(), decoded.LastDeployed.Unix()) + assert.Equal(t, original.Deleted.Unix(), decoded.Deleted.Unix()) + assert.Equal(t, original.Description, decoded.Description) + assert.Equal(t, original.Status, decoded.Status) + assert.Equal(t, original.Notes, decoded.Notes) +} + +func TestInfoEmptyStringRoundTrip(t *testing.T) { + // This test specifically verifies that empty string time fields + // are handled correctly during parsing + input := `{"first_deployed":"","last_deployed":"","deleted":"","status":"deployed","description":"test"}` + + var info Info + err := json.Unmarshal([]byte(input), &info) + require.NoError(t, err) + + // Verify time fields are zero values + assert.True(t, info.FirstDeployed.IsZero()) + assert.True(t, info.LastDeployed.IsZero()) + assert.True(t, info.Deleted.IsZero()) + assert.Equal(t, common.StatusDeployed, info.Status) + assert.Equal(t, "test", info.Description) + + // Marshal back and verify empty time fields are omitted + data, err := json.Marshal(&info) + require.NoError(t, err) + + var result map[string]interface{} + err = json.Unmarshal(data, &result) + require.NoError(t, err) + + // Zero time values should be omitted due to omitzero tag + assert.NotContains(t, result, "first_deployed") + assert.NotContains(t, result, "last_deployed") + assert.NotContains(t, result, "deleted") + assert.Equal(t, "deployed", result["status"]) + assert.Equal(t, "test", result["description"]) +} diff --git a/internal/release/v2/mock.go b/internal/release/v2/mock.go new file mode 100644 index 000000000..b1b06cade --- /dev/null +++ b/internal/release/v2/mock.go @@ -0,0 +1,143 @@ +/* +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 v2 + +import ( + "fmt" + "math/rand" + "time" + + v3 "helm.sh/helm/v4/internal/chart/v3" + "helm.sh/helm/v4/pkg/chart/common" + rcommon "helm.sh/helm/v4/pkg/release/common" +) + +// MockHookTemplate is the hook template used for all mock release objects. +var MockHookTemplate = `apiVersion: v1 +kind: Job +metadata: + annotations: + "helm.sh/hook": pre-install +` + +// MockManifest is the manifest used for all mock release objects. +var MockManifest = `apiVersion: v1 +kind: Secret +metadata: + name: fixture +` + +// MockReleaseOptions allows for user-configurable options on mock release objects. +type MockReleaseOptions struct { + Name string + Version int + Chart *v3.Chart + Status rcommon.Status + Namespace string + Labels map[string]string +} + +// Mock creates a mock release object based on options set by MockReleaseOptions. This function should typically not be used outside of testing. +func Mock(opts *MockReleaseOptions) *Release { + date := time.Unix(242085845, 0).UTC() + + name := opts.Name + if name == "" { + name = "testrelease-" + fmt.Sprint(rand.Intn(100)) + } + + version := 1 + if opts.Version != 0 { + version = opts.Version + } + + namespace := opts.Namespace + if namespace == "" { + namespace = "default" + } + var labels map[string]string + if len(opts.Labels) > 0 { + labels = opts.Labels + } + + ch := opts.Chart + if opts.Chart == nil { + ch = &v3.Chart{ + Metadata: &v3.Metadata{ + Name: "foo", + Version: "0.1.0-beta.1", + AppVersion: "1.0", + APIVersion: v3.APIVersionV3, + Annotations: map[string]string{ + "category": "web-apps", + "supported": "true", + }, + Dependencies: []*v3.Dependency{ + { + Name: "cool-plugin", + Version: "1.0.0", + Repository: "https://coolplugin.io/charts", + Condition: "coolPlugin.enabled", + Enabled: true, + }, + { + Name: "crds", + Version: "2.7.1", + Condition: "crds.enabled", + }, + }, + }, + Templates: []*common.File{ + {Name: "templates/foo.tpl", ModTime: time.Now(), Data: []byte(MockManifest)}, + }, + } + } + + scode := rcommon.StatusDeployed + if len(opts.Status) > 0 { + scode = opts.Status + } + + info := &Info{ + FirstDeployed: date, + LastDeployed: date, + Status: scode, + Description: "Release mock", + Notes: "Some mock release notes!", + } + + return &Release{ + Name: name, + Info: info, + Chart: ch, + Config: map[string]interface{}{"name": "value"}, + Version: version, + Namespace: namespace, + Hooks: []*Hook{ + { + Name: "pre-install-hook", + Kind: "Job", + Path: "pre-install-hook.yaml", + Manifest: MockHookTemplate, + LastRun: HookExecution{}, + Events: []HookEvent{HookPreInstall}, + }, + }, + Manifest: MockManifest, + Labels: labels, + } +} diff --git a/internal/release/v2/release.go b/internal/release/v2/release.go new file mode 100644 index 000000000..85ffb0b2f --- /dev/null +++ b/internal/release/v2/release.go @@ -0,0 +1,60 @@ +/* +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 v2 + +import ( + chart "helm.sh/helm/v4/internal/chart/v3" + "helm.sh/helm/v4/pkg/release/common" +) + +type ApplyMethod string + +const ApplyMethodClientSideApply ApplyMethod = "csa" +const ApplyMethodServerSideApply ApplyMethod = "ssa" + +// Release describes a deployment of a chart, together with the chart +// and the variables used to deploy that chart. +type Release struct { + // Name is the name of the release + Name string `json:"name,omitempty"` + // Info provides information about a release + Info *Info `json:"info,omitempty"` + // Chart is the chart that was released. + Chart *chart.Chart `json:"chart,omitempty"` + // Config is the set of extra Values added to the chart. + // These values override the default values inside of the chart. + Config map[string]interface{} `json:"config,omitempty"` + // Manifest is the string representation of the rendered template. + Manifest string `json:"manifest,omitempty"` + // Hooks are all of the hooks declared for this release. + Hooks []*Hook `json:"hooks,omitempty"` + // Version is an int which represents the revision of the release. + Version int `json:"version,omitempty"` + // Namespace is the kubernetes namespace of the release. + Namespace string `json:"namespace,omitempty"` + // Labels of the release. + // Disabled encoding into Json cause labels are stored in storage driver metadata field. + Labels map[string]string `json:"-"` + // ApplyMethod stores whether server-side or client-side apply was used for the release + // Unset (empty string) should be treated as the default of client-side apply + ApplyMethod string `json:"apply_method,omitempty"` // "ssa" | "csa" +} + +// SetStatus is a helper for setting the status on a release. +func (r *Release) SetStatus(status common.Status, msg string) { + r.Info.Status = status + r.Info.Description = msg +} diff --git a/internal/release/v2/util/filter.go b/internal/release/v2/util/filter.go new file mode 100644 index 000000000..bc568cbdc --- /dev/null +++ b/internal/release/v2/util/filter.go @@ -0,0 +1,81 @@ +/* +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 util // import "helm.sh/helm/v4/internal/release/v2/util" + +import ( + v2 "helm.sh/helm/v4/internal/release/v2" + "helm.sh/helm/v4/pkg/release/common" +) + +// FilterFunc returns true if the release object satisfies +// the predicate of the underlying filter func. +type FilterFunc func(*v2.Release) bool + +// Check applies the FilterFunc to the release object. +func (fn FilterFunc) Check(rls *v2.Release) bool { + if rls == nil { + return false + } + return fn(rls) +} + +// Filter applies the filter(s) to the list of provided releases +// returning the list that satisfies the filtering predicate. +func (fn FilterFunc) Filter(rels []*v2.Release) (rets []*v2.Release) { + for _, rel := range rels { + if fn.Check(rel) { + rets = append(rets, rel) + } + } + return +} + +// Any returns a FilterFunc that filters a list of releases +// determined by the predicate 'f0 || f1 || ... || fn'. +func Any(filters ...FilterFunc) FilterFunc { + return func(rls *v2.Release) bool { + for _, filter := range filters { + if filter(rls) { + return true + } + } + return false + } +} + +// All returns a FilterFunc that filters a list of releases +// determined by the predicate 'f0 && f1 && ... && fn'. +func All(filters ...FilterFunc) FilterFunc { + return func(rls *v2.Release) bool { + for _, filter := range filters { + if !filter(rls) { + return false + } + } + return true + } +} + +// StatusFilter filters a set of releases by status code. +func StatusFilter(status common.Status) FilterFunc { + return FilterFunc(func(rls *v2.Release) bool { + if rls == nil { + return true + } + return rls.Info.Status == status + }) +} diff --git a/internal/release/v2/util/filter_test.go b/internal/release/v2/util/filter_test.go new file mode 100644 index 000000000..35236498a --- /dev/null +++ b/internal/release/v2/util/filter_test.go @@ -0,0 +1,60 @@ +/* +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 util // import "helm.sh/helm/v4/internal/release/v2/util" + +import ( + "testing" + + rspb "helm.sh/helm/v4/internal/release/v2" + "helm.sh/helm/v4/pkg/release/common" +) + +func TestFilterAny(t *testing.T) { + ls := Any(StatusFilter(common.StatusUninstalled)).Filter(releases) + if len(ls) != 2 { + t.Fatalf("expected 2 results, got '%d'", len(ls)) + } + + r0, r1 := ls[0], ls[1] + switch { + case r0.Info.Status != common.StatusUninstalled: + t.Fatalf("expected UNINSTALLED result, got '%s'", r1.Info.Status.String()) + case r1.Info.Status != common.StatusUninstalled: + t.Fatalf("expected UNINSTALLED result, got '%s'", r1.Info.Status.String()) + } +} + +func TestFilterAll(t *testing.T) { + fn := FilterFunc(func(rls *rspb.Release) bool { + // true if not uninstalled and version < 4 + v0 := !StatusFilter(common.StatusUninstalled).Check(rls) + v1 := rls.Version < 4 + return v0 && v1 + }) + + ls := All(fn).Filter(releases) + if len(ls) != 1 { + t.Fatalf("expected 1 result, got '%d'", len(ls)) + } + + switch r0 := ls[0]; { + case r0.Version == 4: + t.Fatal("got release with status revision 4") + case r0.Info.Status == common.StatusUninstalled: + t.Fatal("got release with status UNINSTALLED") + } +} diff --git a/internal/release/v2/util/kind_sorter.go b/internal/release/v2/util/kind_sorter.go new file mode 100644 index 000000000..71bacb228 --- /dev/null +++ b/internal/release/v2/util/kind_sorter.go @@ -0,0 +1,165 @@ +/* +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 util // import "helm.sh/helm/v4/internal/release/v2/util" + +import ( + "sort" + + release "helm.sh/helm/v4/internal/release/v2" +) + +// KindSortOrder is an ordering of Kinds. +type KindSortOrder []string + +// InstallOrder is the order in which manifests should be installed (by Kind). +// +// Those occurring earlier in the list get installed before those occurring later in the list. +var InstallOrder KindSortOrder = []string{ + "PriorityClass", + "Namespace", + "NetworkPolicy", + "ResourceQuota", + "LimitRange", + "PodSecurityPolicy", + "PodDisruptionBudget", + "ServiceAccount", + "Secret", + "SecretList", + "ConfigMap", + "StorageClass", + "PersistentVolume", + "PersistentVolumeClaim", + "CustomResourceDefinition", + "ClusterRole", + "ClusterRoleList", + "ClusterRoleBinding", + "ClusterRoleBindingList", + "Role", + "RoleList", + "RoleBinding", + "RoleBindingList", + "Service", + "DaemonSet", + "Pod", + "ReplicationController", + "ReplicaSet", + "Deployment", + "HorizontalPodAutoscaler", + "StatefulSet", + "Job", + "CronJob", + "IngressClass", + "Ingress", + "APIService", + "MutatingWebhookConfiguration", + "ValidatingWebhookConfiguration", +} + +// UninstallOrder is the order in which manifests should be uninstalled (by Kind). +// +// Those occurring earlier in the list get uninstalled before those occurring later in the list. +var UninstallOrder KindSortOrder = []string{ + // For uninstall, we remove validation before mutation to ensure webhooks don't block removal + "ValidatingWebhookConfiguration", + "MutatingWebhookConfiguration", + "APIService", + "Ingress", + "IngressClass", + "Service", + "CronJob", + "Job", + "StatefulSet", + "HorizontalPodAutoscaler", + "Deployment", + "ReplicaSet", + "ReplicationController", + "Pod", + "DaemonSet", + "RoleBindingList", + "RoleBinding", + "RoleList", + "Role", + "ClusterRoleBindingList", + "ClusterRoleBinding", + "ClusterRoleList", + "ClusterRole", + "CustomResourceDefinition", + "PersistentVolumeClaim", + "PersistentVolume", + "StorageClass", + "ConfigMap", + "SecretList", + "Secret", + "ServiceAccount", + "PodDisruptionBudget", + "PodSecurityPolicy", + "LimitRange", + "ResourceQuota", + "NetworkPolicy", + "Namespace", + "PriorityClass", +} + +// sort manifests by kind. +// +// Results are sorted by 'ordering', keeping order of items with equal kind/priority +func sortManifestsByKind(manifests []Manifest, ordering KindSortOrder) []Manifest { + sort.SliceStable(manifests, func(i, j int) bool { + return lessByKind(manifests[i], manifests[j], manifests[i].Head.Kind, manifests[j].Head.Kind, ordering) + }) + + return manifests +} + +// sort hooks by kind, using an out-of-place sort to preserve the input parameters. +// +// Results are sorted by 'ordering', keeping order of items with equal kind/priority +func sortHooksByKind(hooks []*release.Hook, ordering KindSortOrder) []*release.Hook { + h := hooks + sort.SliceStable(h, func(i, j int) bool { + return lessByKind(h[i], h[j], h[i].Kind, h[j].Kind, ordering) + }) + + return h +} + +func lessByKind(_ interface{}, _ interface{}, kindA string, kindB string, o KindSortOrder) bool { + ordering := make(map[string]int, len(o)) + for v, k := range o { + ordering[k] = v + } + + first, aok := ordering[kindA] + second, bok := ordering[kindB] + + if !aok && !bok { + // if both are unknown then sort alphabetically by kind, keep original order if same kind + if kindA != kindB { + return kindA < kindB + } + return first < second + } + // unknown kind is last + if !aok { + return false + } + if !bok { + return true + } + // sort different kinds, keep original order if same priority + return first < second +} diff --git a/internal/release/v2/util/kind_sorter_test.go b/internal/release/v2/util/kind_sorter_test.go new file mode 100644 index 000000000..06418a5fc --- /dev/null +++ b/internal/release/v2/util/kind_sorter_test.go @@ -0,0 +1,347 @@ +/* +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 util // import "helm.sh/helm/v4/internal/release/v2/util" + +import ( + "bytes" + "testing" + + release "helm.sh/helm/v4/internal/release/v2" +) + +func TestKindSorter(t *testing.T) { + manifests := []Manifest{ + { + Name: "U", + Head: &SimpleHead{Kind: "IngressClass"}, + }, + { + Name: "E", + Head: &SimpleHead{Kind: "SecretList"}, + }, + { + Name: "i", + Head: &SimpleHead{Kind: "ClusterRole"}, + }, + { + Name: "I", + Head: &SimpleHead{Kind: "ClusterRoleList"}, + }, + { + Name: "j", + Head: &SimpleHead{Kind: "ClusterRoleBinding"}, + }, + { + Name: "J", + Head: &SimpleHead{Kind: "ClusterRoleBindingList"}, + }, + { + Name: "f", + Head: &SimpleHead{Kind: "ConfigMap"}, + }, + { + Name: "u", + Head: &SimpleHead{Kind: "CronJob"}, + }, + { + Name: "2", + Head: &SimpleHead{Kind: "CustomResourceDefinition"}, + }, + { + Name: "n", + Head: &SimpleHead{Kind: "DaemonSet"}, + }, + { + Name: "r", + Head: &SimpleHead{Kind: "Deployment"}, + }, + { + Name: "!", + Head: &SimpleHead{Kind: "HonkyTonkSet"}, + }, + { + Name: "v", + Head: &SimpleHead{Kind: "Ingress"}, + }, + { + Name: "t", + Head: &SimpleHead{Kind: "Job"}, + }, + { + Name: "c", + Head: &SimpleHead{Kind: "LimitRange"}, + }, + { + Name: "a", + Head: &SimpleHead{Kind: "Namespace"}, + }, + { + Name: "A", + Head: &SimpleHead{Kind: "NetworkPolicy"}, + }, + { + Name: "g", + Head: &SimpleHead{Kind: "PersistentVolume"}, + }, + { + Name: "h", + Head: &SimpleHead{Kind: "PersistentVolumeClaim"}, + }, + { + Name: "o", + Head: &SimpleHead{Kind: "Pod"}, + }, + { + Name: "3", + Head: &SimpleHead{Kind: "PodDisruptionBudget"}, + }, + { + Name: "C", + Head: &SimpleHead{Kind: "PodSecurityPolicy"}, + }, + { + Name: "q", + Head: &SimpleHead{Kind: "ReplicaSet"}, + }, + { + Name: "p", + Head: &SimpleHead{Kind: "ReplicationController"}, + }, + { + Name: "b", + Head: &SimpleHead{Kind: "ResourceQuota"}, + }, + { + Name: "k", + Head: &SimpleHead{Kind: "Role"}, + }, + { + Name: "K", + Head: &SimpleHead{Kind: "RoleList"}, + }, + { + Name: "l", + Head: &SimpleHead{Kind: "RoleBinding"}, + }, + { + Name: "L", + Head: &SimpleHead{Kind: "RoleBindingList"}, + }, + { + Name: "e", + Head: &SimpleHead{Kind: "Secret"}, + }, + { + Name: "m", + Head: &SimpleHead{Kind: "Service"}, + }, + { + Name: "d", + Head: &SimpleHead{Kind: "ServiceAccount"}, + }, + { + Name: "s", + Head: &SimpleHead{Kind: "StatefulSet"}, + }, + { + Name: "1", + Head: &SimpleHead{Kind: "StorageClass"}, + }, + { + Name: "w", + Head: &SimpleHead{Kind: "APIService"}, + }, + { + Name: "x", + Head: &SimpleHead{Kind: "HorizontalPodAutoscaler"}, + }, + { + Name: "F", + Head: &SimpleHead{Kind: "PriorityClass"}, + }, + { + Name: "M", + Head: &SimpleHead{Kind: "MutatingWebhookConfiguration"}, + }, + { + Name: "V", + Head: &SimpleHead{Kind: "ValidatingWebhookConfiguration"}, + }, + } + + for _, test := range []struct { + description string + order KindSortOrder + expected string + }{ + {"install", InstallOrder, "FaAbcC3deEf1gh2iIjJkKlLmnopqrxstuUvwMV!"}, + {"uninstall", UninstallOrder, "VMwvUmutsxrqponLlKkJjIi2hg1fEed3CcbAaF!"}, + } { + var buf bytes.Buffer + t.Run(test.description, func(t *testing.T) { + if got, want := len(test.expected), len(manifests); got != want { + t.Fatalf("Expected %d names in order, got %d", want, got) + } + defer buf.Reset() + orig := manifests + for _, r := range sortManifestsByKind(manifests, test.order) { + buf.WriteString(r.Name) + } + if got := buf.String(); got != test.expected { + t.Errorf("Expected %q, got %q", test.expected, got) + } + for i, manifest := range orig { + if manifest != manifests[i] { + t.Fatal("Expected input to sortManifestsByKind to stay the same") + } + } + }) + } +} + +// TestKindSorterKeepOriginalOrder verifies manifests of same kind are kept in original order +func TestKindSorterKeepOriginalOrder(t *testing.T) { + manifests := []Manifest{ + { + Name: "a", + Head: &SimpleHead{Kind: "ClusterRole"}, + }, + { + Name: "A", + Head: &SimpleHead{Kind: "ClusterRole"}, + }, + { + Name: "0", + Head: &SimpleHead{Kind: "ConfigMap"}, + }, + { + Name: "1", + Head: &SimpleHead{Kind: "ConfigMap"}, + }, + { + Name: "z", + Head: &SimpleHead{Kind: "ClusterRoleBinding"}, + }, + { + Name: "!", + Head: &SimpleHead{Kind: "ClusterRoleBinding"}, + }, + { + Name: "u2", + Head: &SimpleHead{Kind: "Unknown"}, + }, + { + Name: "u1", + Head: &SimpleHead{Kind: "Unknown"}, + }, + { + Name: "t3", + Head: &SimpleHead{Kind: "Unknown2"}, + }, + } + for _, test := range []struct { + description string + order KindSortOrder + expected string + }{ + // expectation is sorted by kind (unknown is last) and within each group of same kind, the order is kept + {"cm,clusterRole,clusterRoleBinding,Unknown,Unknown2", InstallOrder, "01aAz!u2u1t3"}, + } { + var buf bytes.Buffer + t.Run(test.description, func(t *testing.T) { + defer buf.Reset() + for _, r := range sortManifestsByKind(manifests, test.order) { + buf.WriteString(r.Name) + } + if got := buf.String(); got != test.expected { + t.Errorf("Expected %q, got %q", test.expected, got) + } + }) + } +} + +func TestKindSorterNamespaceAgainstUnknown(t *testing.T) { + unknown := Manifest{ + Name: "a", + Head: &SimpleHead{Kind: "Unknown"}, + } + namespace := Manifest{ + Name: "b", + Head: &SimpleHead{Kind: "Namespace"}, + } + + manifests := []Manifest{unknown, namespace} + manifests = sortManifestsByKind(manifests, InstallOrder) + + expectedOrder := []Manifest{namespace, unknown} + for i, manifest := range manifests { + if expectedOrder[i].Name != manifest.Name { + t.Errorf("Expected %s, got %s", expectedOrder[i].Name, manifest.Name) + } + } +} + +// test hook sorting with a small subset of kinds, since it uses the same algorithm as sortManifestsByKind +func TestKindSorterForHooks(t *testing.T) { + hooks := []*release.Hook{ + { + Name: "i", + Kind: "ClusterRole", + }, + { + Name: "j", + Kind: "ClusterRoleBinding", + }, + { + Name: "c", + Kind: "LimitRange", + }, + { + Name: "a", + Kind: "Namespace", + }, + } + + for _, test := range []struct { + description string + order KindSortOrder + expected string + }{ + {"install", InstallOrder, "acij"}, + {"uninstall", UninstallOrder, "jica"}, + } { + var buf bytes.Buffer + t.Run(test.description, func(t *testing.T) { + if got, want := len(test.expected), len(hooks); got != want { + t.Fatalf("Expected %d names in order, got %d", want, got) + } + defer buf.Reset() + orig := hooks + for _, r := range sortHooksByKind(hooks, test.order) { + buf.WriteString(r.Name) + } + for i, hook := range orig { + if hook != hooks[i] { + t.Fatal("Expected input to sortHooksByKind to stay the same") + } + } + if got := buf.String(); got != test.expected { + t.Errorf("Expected %q, got %q", test.expected, got) + } + }) + } +} diff --git a/internal/release/v2/util/manifest.go b/internal/release/v2/util/manifest.go new file mode 100644 index 000000000..20d097d9b --- /dev/null +++ b/internal/release/v2/util/manifest.go @@ -0,0 +1,72 @@ +/* +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 util // import "helm.sh/helm/v4/internal/release/v2/util" + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +// SimpleHead defines what the structure of the head of a manifest file +type SimpleHead struct { + Version string `json:"apiVersion"` + Kind string `json:"kind,omitempty"` + Metadata *struct { + Name string `json:"name"` + Annotations map[string]string `json:"annotations"` + } `json:"metadata,omitempty"` +} + +var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") + +// SplitManifests takes a string of manifest and returns a map contains individual manifests +func SplitManifests(bigFile string) map[string]string { + // Basically, we're quickly splitting a stream of YAML documents into an + // array of YAML docs. The file name is just a place holder, but should be + // integer-sortable so that manifests get output in the same order as the + // input (see `BySplitManifestsOrder`). + tpl := "manifest-%d" + res := map[string]string{} + // Making sure that any extra whitespace in YAML stream doesn't interfere in splitting documents correctly. + bigFileTmp := strings.TrimSpace(bigFile) + docs := sep.Split(bigFileTmp, -1) + var count int + for _, d := range docs { + if d == "" { + continue + } + + d = strings.TrimSpace(d) + res[fmt.Sprintf(tpl, count)] = d + count = count + 1 + } + return res +} + +// BySplitManifestsOrder sorts by in-file manifest order, as provided in function `SplitManifests` +type BySplitManifestsOrder []string + +func (a BySplitManifestsOrder) Len() int { return len(a) } +func (a BySplitManifestsOrder) Less(i, j int) bool { + // Split `manifest-%d` + anum, _ := strconv.ParseInt(a[i][len("manifest-"):], 10, 0) + bnum, _ := strconv.ParseInt(a[j][len("manifest-"):], 10, 0) + return anum < bnum +} +func (a BySplitManifestsOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } diff --git a/internal/release/v2/util/manifest_sorter.go b/internal/release/v2/util/manifest_sorter.go new file mode 100644 index 000000000..f269dda6d --- /dev/null +++ b/internal/release/v2/util/manifest_sorter.go @@ -0,0 +1,244 @@ +/* +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 util // import "helm.sh/helm/v4/internal/release/v2/util" + +import ( + "fmt" + "log/slog" + "path" + "sort" + "strconv" + "strings" + + "sigs.k8s.io/yaml" + + v2 "helm.sh/helm/v4/internal/release/v2" + "helm.sh/helm/v4/pkg/chart/common" +) + +// Manifest represents a manifest file, which has a name and some content. +type Manifest struct { + Name string + Content string + Head *SimpleHead +} + +// manifestFile represents a file that contains a manifest. +type manifestFile struct { + entries map[string]string + path string +} + +// result is an intermediate structure used during sorting. +type result struct { + hooks []*v2.Hook + generic []Manifest +} + +// TODO: Refactor this out. It's here because naming conventions were not followed through. +// So fix the Test hook names and then remove this. +var events = map[string]v2.HookEvent{ + v2.HookPreInstall.String(): v2.HookPreInstall, + v2.HookPostInstall.String(): v2.HookPostInstall, + v2.HookPreDelete.String(): v2.HookPreDelete, + v2.HookPostDelete.String(): v2.HookPostDelete, + v2.HookPreUpgrade.String(): v2.HookPreUpgrade, + v2.HookPostUpgrade.String(): v2.HookPostUpgrade, + v2.HookPreRollback.String(): v2.HookPreRollback, + v2.HookPostRollback.String(): v2.HookPostRollback, + v2.HookTest.String(): v2.HookTest, + // Support test-success for backward compatibility with Helm 2 tests + "test-success": v2.HookTest, +} + +// SortManifests takes a map of filename/YAML contents, splits the file +// by manifest entries, and sorts the entries into hook types. +// +// The resulting hooks struct will be populated with all of the generated hooks. +// Any file that does not declare one of the hook types will be placed in the +// 'generic' bucket. +// +// Files that do not parse into the expected format are simply placed into a map and +// returned. +func SortManifests(files map[string]string, _ common.VersionSet, ordering KindSortOrder) ([]*v2.Hook, []Manifest, error) { + result := &result{} + + var sortedFilePaths []string + for filePath := range files { + sortedFilePaths = append(sortedFilePaths, filePath) + } + sort.Strings(sortedFilePaths) + + for _, filePath := range sortedFilePaths { + content := files[filePath] + + // Skip partials. We could return these as a separate map, but there doesn't + // seem to be any need for that at this time. + if strings.HasPrefix(path.Base(filePath), "_") { + continue + } + // Skip empty files and log this. + if strings.TrimSpace(content) == "" { + continue + } + + manifestFile := &manifestFile{ + entries: SplitManifests(content), + path: filePath, + } + + if err := manifestFile.sort(result); err != nil { + return result.hooks, result.generic, err + } + } + + return sortHooksByKind(result.hooks, ordering), sortManifestsByKind(result.generic, ordering), nil +} + +// sort takes a manifestFile object which may contain multiple resource definition +// entries and sorts each entry by hook types, and saves the resulting hooks and +// generic manifests (or non-hooks) to the result struct. +// +// To determine hook type, it looks for a YAML structure like this: +// +// kind: SomeKind +// apiVersion: v1 +// metadata: +// annotations: +// helm.sh/hook: pre-install +// +// To determine the policy to delete the hook, it looks for a YAML structure like this: +// +// kind: SomeKind +// apiVersion: v1 +// metadata: +// annotations: +// helm.sh/hook-delete-policy: hook-succeeded +// +// To determine the policy to output logs of the hook (for Pod and Job only), it looks for a YAML structure like this: +// +// kind: Pod +// apiVersion: v1 +// metadata: +// annotations: +// helm.sh/hook-output-log-policy: hook-succeeded,hook-failed +func (file *manifestFile) sort(result *result) error { + // Go through manifests in order found in file (function `SplitManifests` creates integer-sortable keys) + var sortedEntryKeys []string + for entryKey := range file.entries { + sortedEntryKeys = append(sortedEntryKeys, entryKey) + } + sort.Sort(BySplitManifestsOrder(sortedEntryKeys)) + + for _, entryKey := range sortedEntryKeys { + m := file.entries[entryKey] + + var entry SimpleHead + if err := yaml.Unmarshal([]byte(m), &entry); err != nil { + return fmt.Errorf("YAML parse error on %s: %w", file.path, err) + } + + if !hasAnyAnnotation(entry) { + result.generic = append(result.generic, Manifest{ + Name: file.path, + Content: m, + Head: &entry, + }) + continue + } + + hookTypes, ok := entry.Metadata.Annotations[v2.HookAnnotation] + if !ok { + result.generic = append(result.generic, Manifest{ + Name: file.path, + Content: m, + Head: &entry, + }) + continue + } + + hw := calculateHookWeight(entry) + + h := &v2.Hook{ + Name: entry.Metadata.Name, + Kind: entry.Kind, + Path: file.path, + Manifest: m, + Events: []v2.HookEvent{}, + Weight: hw, + DeletePolicies: []v2.HookDeletePolicy{}, + OutputLogPolicies: []v2.HookOutputLogPolicy{}, + } + + isUnknownHook := false + for hookType := range strings.SplitSeq(hookTypes, ",") { + hookType = strings.ToLower(strings.TrimSpace(hookType)) + e, ok := events[hookType] + if !ok { + isUnknownHook = true + break + } + h.Events = append(h.Events, e) + } + + if isUnknownHook { + slog.Info("skipping unknown hooks", "hookTypes", hookTypes) + continue + } + + result.hooks = append(result.hooks, h) + + operateAnnotationValues(entry, v2.HookDeleteAnnotation, func(value string) { + h.DeletePolicies = append(h.DeletePolicies, v2.HookDeletePolicy(value)) + }) + + operateAnnotationValues(entry, v2.HookOutputLogAnnotation, func(value string) { + h.OutputLogPolicies = append(h.OutputLogPolicies, v2.HookOutputLogPolicy(value)) + }) + } + + return nil +} + +// hasAnyAnnotation returns true if the given entry has any annotations at all. +func hasAnyAnnotation(entry SimpleHead) bool { + return entry.Metadata != nil && + entry.Metadata.Annotations != nil && + len(entry.Metadata.Annotations) != 0 +} + +// calculateHookWeight finds the weight in the hook weight annotation. +// +// If no weight is found, the assigned weight is 0 +func calculateHookWeight(entry SimpleHead) int { + hws := entry.Metadata.Annotations[v2.HookWeightAnnotation] + hw, err := strconv.Atoi(hws) + if err != nil { + hw = 0 + } + return hw +} + +// operateAnnotationValues finds the given annotation and runs the operate function with the value of that annotation +func operateAnnotationValues(entry SimpleHead, annotation string, operate func(p string)) { + if dps, ok := entry.Metadata.Annotations[annotation]; ok { + for dp := range strings.SplitSeq(dps, ",") { + dp = strings.ToLower(strings.TrimSpace(dp)) + operate(dp) + } + } +} diff --git a/internal/release/v2/util/manifest_sorter_test.go b/internal/release/v2/util/manifest_sorter_test.go new file mode 100644 index 000000000..28f0b34cc --- /dev/null +++ b/internal/release/v2/util/manifest_sorter_test.go @@ -0,0 +1,227 @@ +/* +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 util // import "helm.sh/helm/v4/internal/release/v2/util" + +import ( + "reflect" + "testing" + + "sigs.k8s.io/yaml" + + release "helm.sh/helm/v4/internal/release/v2" +) + +func TestSortManifests(t *testing.T) { + + data := []struct { + name []string + path string + kind []string + hooks map[string][]release.HookEvent + manifest string + }{ + { + name: []string{"first"}, + path: "one", + kind: []string{"Job"}, + hooks: map[string][]release.HookEvent{"first": {release.HookPreInstall}}, + manifest: `apiVersion: v1 +kind: Job +metadata: + name: first + labels: + doesnot: matter + annotations: + "helm.sh/hook": pre-install +`, + }, + { + name: []string{"second"}, + path: "two", + kind: []string{"ReplicaSet"}, + hooks: map[string][]release.HookEvent{"second": {release.HookPostInstall}}, + manifest: `kind: ReplicaSet +apiVersion: v1beta1 +metadata: + name: second + annotations: + "helm.sh/hook": post-install +`, + }, { + name: []string{"third"}, + path: "three", + kind: []string{"ReplicaSet"}, + hooks: map[string][]release.HookEvent{"third": nil}, + manifest: `kind: ReplicaSet +apiVersion: v1beta1 +metadata: + name: third + annotations: + "helm.sh/hook": no-such-hook +`, + }, { + name: []string{"fourth"}, + path: "four", + kind: []string{"Pod"}, + hooks: map[string][]release.HookEvent{"fourth": nil}, + manifest: `kind: Pod +apiVersion: v1 +metadata: + name: fourth + annotations: + nothing: here`, + }, { + name: []string{"fifth"}, + path: "five", + kind: []string{"ReplicaSet"}, + hooks: map[string][]release.HookEvent{"fifth": {release.HookPostDelete, release.HookPostInstall}}, + manifest: `kind: ReplicaSet +apiVersion: v1beta1 +metadata: + name: fifth + annotations: + "helm.sh/hook": post-delete, post-install +`, + }, { + // Regression test: files with an underscore in the base name should be skipped. + name: []string{"sixth"}, + path: "six/_six", + kind: []string{"ReplicaSet"}, + hooks: map[string][]release.HookEvent{"sixth": nil}, + manifest: `invalid manifest`, // This will fail if partial is not skipped. + }, { + // Regression test: files with no content should be skipped. + name: []string{"seventh"}, + path: "seven", + kind: []string{"ReplicaSet"}, + hooks: map[string][]release.HookEvent{"seventh": nil}, + manifest: "", + }, + { + name: []string{"eighth", "example-test"}, + path: "eight", + kind: []string{"ConfigMap", "Pod"}, + hooks: map[string][]release.HookEvent{"eighth": nil, "example-test": {release.HookTest}}, + manifest: `kind: ConfigMap +apiVersion: v1 +metadata: + name: eighth +data: + name: value +--- +apiVersion: v1 +kind: Pod +metadata: + name: example-test + annotations: + "helm.sh/hook": test +`, + }, + } + + manifests := make(map[string]string, len(data)) + for _, o := range data { + manifests[o.path] = o.manifest + } + + hs, generic, err := SortManifests(manifests, nil, InstallOrder) + if err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + // This test will fail if 'six' or 'seven' was added. + if len(generic) != 2 { + t.Errorf("Expected 2 generic manifests, got %d", len(generic)) + } + + if len(hs) != 4 { + t.Errorf("Expected 4 hooks, got %d", len(hs)) + } + + for _, out := range hs { + found := false + for _, expect := range data { + if out.Path == expect.path { + found = true + if out.Path != expect.path { + t.Errorf("Expected path %s, got %s", expect.path, out.Path) + } + nameFound := false + for _, expectedName := range expect.name { + if out.Name == expectedName { + nameFound = true + } + } + if !nameFound { + t.Errorf("Got unexpected name %s", out.Name) + } + kindFound := false + for _, expectedKind := range expect.kind { + if out.Kind == expectedKind { + kindFound = true + } + } + if !kindFound { + t.Errorf("Got unexpected kind %s", out.Kind) + } + + expectedHooks := expect.hooks[out.Name] + if !reflect.DeepEqual(expectedHooks, out.Events) { + t.Errorf("expected events: %v but got: %v", expectedHooks, out.Events) + } + + } + } + if !found { + t.Errorf("Result not found: %v", out) + } + } + + // Verify the sort order + sorted := []Manifest{} + for _, s := range data { + manifests := SplitManifests(s.manifest) + + for _, m := range manifests { + var sh SimpleHead + if err := yaml.Unmarshal([]byte(m), &sh); err != nil { + // This is expected for manifests that are corrupt or empty. + t.Log(err) + continue + } + + name := sh.Metadata.Name + + // only keep track of non-hook manifests + if s.hooks[name] == nil { + another := Manifest{ + Content: m, + Name: name, + Head: &sh, + } + sorted = append(sorted, another) + } + } + } + + sorted = sortManifestsByKind(sorted, InstallOrder) + for i, m := range generic { + if m.Content != sorted[i].Content { + t.Errorf("Expected %q, got %q", m.Content, sorted[i].Content) + } + } +} diff --git a/internal/release/v2/util/manifest_test.go b/internal/release/v2/util/manifest_test.go new file mode 100644 index 000000000..7fd332fbc --- /dev/null +++ b/internal/release/v2/util/manifest_test.go @@ -0,0 +1,61 @@ +/* +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 util // import "helm.sh/helm/v4/internal/release/v2/util" + +import ( + "reflect" + "testing" +) + +const mockManifestFile = ` + +--- +apiVersion: v1 +kind: Pod +metadata: + name: finding-nemo, + annotations: + "helm.sh/hook": test +spec: + containers: + - name: nemo-test + image: fake-image + cmd: fake-command +` + +const expectedManifest = `apiVersion: v1 +kind: Pod +metadata: + name: finding-nemo, + annotations: + "helm.sh/hook": test +spec: + containers: + - name: nemo-test + image: fake-image + cmd: fake-command` + +func TestSplitManifest(t *testing.T) { + manifests := SplitManifests(mockManifestFile) + if len(manifests) != 1 { + t.Errorf("Expected 1 manifest, got %v", len(manifests)) + } + expected := map[string]string{"manifest-0": expectedManifest} + if !reflect.DeepEqual(manifests, expected) { + t.Errorf("Expected %v, got %v", expected, manifests) + } +} diff --git a/internal/release/v2/util/sorter.go b/internal/release/v2/util/sorter.go new file mode 100644 index 000000000..5f13569c1 --- /dev/null +++ b/internal/release/v2/util/sorter.go @@ -0,0 +1,61 @@ +/* +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 util // import "helm.sh/helm/v4/internal/release/v2/util" + +import ( + "sort" + + rspb "helm.sh/helm/v4/internal/release/v2" +) + +// Reverse reverses the list of releases sorted by the sort func. +func Reverse(list []*rspb.Release, sortFn func([]*rspb.Release)) { + sortFn(list) + for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 { + list[i], list[j] = list[j], list[i] + } +} + +// SortByName returns the list of releases sorted +// in lexicographical order. +func SortByName(list []*rspb.Release) { + sort.Slice(list, func(i, j int) bool { + return list[i].Name < list[j].Name + }) +} + +// SortByDate returns the list of releases sorted by a +// release's last deployed time (in seconds). +func SortByDate(list []*rspb.Release) { + sort.Slice(list, func(i, j int) bool { + ti := list[i].Info.LastDeployed.Unix() + tj := list[j].Info.LastDeployed.Unix() + if ti != tj { + return ti < tj + } + // Use name as tie-breaker for stable sorting + return list[i].Name < list[j].Name + }) +} + +// SortByRevision returns the list of releases sorted by a +// release's revision number (release.Version). +func SortByRevision(list []*rspb.Release) { + sort.Slice(list, func(i, j int) bool { + return list[i].Version < list[j].Version + }) +} diff --git a/internal/release/v2/util/sorter_test.go b/internal/release/v2/util/sorter_test.go new file mode 100644 index 000000000..6cb876f69 --- /dev/null +++ b/internal/release/v2/util/sorter_test.go @@ -0,0 +1,109 @@ +/* +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 util // import "helm.sh/helm/v4/internal/release/v2/util" + +import ( + "testing" + "time" + + rspb "helm.sh/helm/v4/internal/release/v2" + "helm.sh/helm/v4/pkg/release/common" +) + +// note: this test data is shared with filter_test.go. + +var releases = []*rspb.Release{ + tsRelease("quiet-bear", 2, 2000, common.StatusSuperseded), + tsRelease("angry-bird", 4, 3000, common.StatusDeployed), + tsRelease("happy-cats", 1, 4000, common.StatusUninstalled), + tsRelease("vocal-dogs", 3, 6000, common.StatusUninstalled), +} + +func tsRelease(name string, vers int, dur time.Duration, status common.Status) *rspb.Release { + info := &rspb.Info{Status: status, LastDeployed: time.Now().Add(dur)} + return &rspb.Release{ + Name: name, + Version: vers, + Info: info, + } +} + +func check(t *testing.T, by string, fn func(int, int) bool) { + t.Helper() + for i := len(releases) - 1; i > 0; i-- { + if fn(i, i-1) { + t.Errorf("release at positions '(%d,%d)' not sorted by %s", i-1, i, by) + } + } +} + +func TestSortByName(t *testing.T) { + SortByName(releases) + + check(t, "ByName", func(i, j int) bool { + ni := releases[i].Name + nj := releases[j].Name + return ni < nj + }) +} + +func TestSortByDate(t *testing.T) { + SortByDate(releases) + + check(t, "ByDate", func(i, j int) bool { + ti := releases[i].Info.LastDeployed.Second() + tj := releases[j].Info.LastDeployed.Second() + return ti < tj + }) +} + +func TestSortByRevision(t *testing.T) { + SortByRevision(releases) + + check(t, "ByRevision", func(i, j int) bool { + vi := releases[i].Version + vj := releases[j].Version + return vi < vj + }) +} + +func TestReverseSortByName(t *testing.T) { + Reverse(releases, SortByName) + check(t, "ByName", func(i, j int) bool { + ni := releases[i].Name + nj := releases[j].Name + return ni > nj + }) +} + +func TestReverseSortByDate(t *testing.T) { + Reverse(releases, SortByDate) + check(t, "ByDate", func(i, j int) bool { + ti := releases[i].Info.LastDeployed.Second() + tj := releases[j].Info.LastDeployed.Second() + return ti > tj + }) +} + +func TestReverseSortByRevision(t *testing.T) { + Reverse(releases, SortByRevision) + check(t, "ByRevision", func(i, j int) bool { + vi := releases[i].Version + vj := releases[j].Version + return vi > vj + }) +} diff --git a/pkg/release/common.go b/pkg/release/common.go index d33c96646..7d40effdc 100644 --- a/pkg/release/common.go +++ b/pkg/release/common.go @@ -21,6 +21,7 @@ import ( "fmt" "time" + v2release "helm.sh/helm/v4/internal/release/v2" "helm.sh/helm/v4/pkg/chart" v1release "helm.sh/helm/v4/pkg/release/v1" ) @@ -35,6 +36,10 @@ func newDefaultAccessor(rel Releaser) (Accessor, error) { return &v1Accessor{&v}, nil case *v1release.Release: return &v1Accessor{v}, nil + case v2release.Release: + return &v2Accessor{&v}, nil + case *v2release.Release: + return &v2Accessor{v}, nil default: return nil, fmt.Errorf("unsupported release type: %T", rel) } @@ -46,6 +51,10 @@ func newDefaultHookAccessor(hook Hook) (HookAccessor, error) { return &v1HookAccessor{&h}, nil case *v1release.Hook: return &v1HookAccessor{h}, nil + case v2release.Hook: + return &v2HookAccessor{&h}, nil + case *v2release.Hook: + return &v2HookAccessor{h}, nil default: return nil, errors.New("unsupported release hook type") } @@ -114,3 +123,67 @@ func (a *v1HookAccessor) Path() string { func (a *v1HookAccessor) Manifest() string { return a.hook.Manifest } + +type v2Accessor struct { + rel *v2release.Release +} + +func (a *v2Accessor) Name() string { + return a.rel.Name +} + +func (a *v2Accessor) Namespace() string { + return a.rel.Namespace +} + +func (a *v2Accessor) Version() int { + return a.rel.Version +} + +func (a *v2Accessor) Hooks() []Hook { + var hooks = make([]Hook, len(a.rel.Hooks)) + for i, h := range a.rel.Hooks { + hooks[i] = h + } + return hooks +} + +func (a *v2Accessor) Manifest() string { + return a.rel.Manifest +} + +func (a *v2Accessor) Notes() string { + return a.rel.Info.Notes +} + +func (a *v2Accessor) Labels() map[string]string { + return a.rel.Labels +} + +func (a *v2Accessor) Chart() chart.Charter { + return a.rel.Chart +} + +func (a *v2Accessor) Status() string { + return a.rel.Info.Status.String() +} + +func (a *v2Accessor) ApplyMethod() string { + return a.rel.ApplyMethod +} + +func (a *v2Accessor) DeployedAt() time.Time { + return a.rel.Info.LastDeployed +} + +type v2HookAccessor struct { + hook *v2release.Hook +} + +func (a *v2HookAccessor) Path() string { + return a.hook.Path +} + +func (a *v2HookAccessor) Manifest() string { + return a.hook.Manifest +} diff --git a/pkg/release/common_test.go b/pkg/release/common_test.go index e9f8d364a..bc22971e9 100644 --- a/pkg/release/common_test.go +++ b/pkg/release/common_test.go @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/assert" + v2release "helm.sh/helm/v4/internal/release/v2" "helm.sh/helm/v4/pkg/release/common" rspb "helm.sh/helm/v4/pkg/release/v1" ) @@ -63,3 +64,72 @@ func TestNewDefaultAccessor(t *testing.T) { is.Equal(rel.ApplyMethod, accessor.ApplyMethod()) is.Equal(rel.Labels, accessor.Labels()) } + +func TestNewDefaultAccessorV2(t *testing.T) { + // Testing the default implementation for v2 releases (charts/v3) + is := assert.New(t) + + // Create v2 release + info := &v2release.Info{Status: common.StatusDeployed, LastDeployed: time.Now().Add(1000), Notes: "test notes"} + labels := make(map[string]string) + labels["foo"] = "bar" + rel := &v2release.Release{ + Name: "happy-cats-v2", + Version: 3, + Info: info, + Labels: labels, + Namespace: "test-namespace", + ApplyMethod: "ssa", + Manifest: "test manifest content", + Hooks: []*v2release.Hook{ + { + Name: "test-hook", + Kind: "Job", + Path: "templates/hook.yaml", + Manifest: "hook manifest", + }, + }, + } + + // Test accessor creation + accessor, err := newDefaultAccessor(rel) + is.NoError(err) + + // Verify all accessor methods return correct values + is.Equal(rel.Name, accessor.Name()) + is.Equal(rel.Namespace, accessor.Namespace()) + is.Equal(rel.Version, accessor.Version()) + is.Equal(rel.ApplyMethod, accessor.ApplyMethod()) + is.Equal(rel.Labels, accessor.Labels()) + is.Equal(rel.Manifest, accessor.Manifest()) + is.Equal(rel.Info.Notes, accessor.Notes()) + is.Equal(rel.Info.Status.String(), accessor.Status()) + is.Equal(rel.Info.LastDeployed, accessor.DeployedAt()) + + // Verify hooks are accessible + hooks := accessor.Hooks() + is.Len(hooks, 1) + + // Test hook accessor + hookAccessor, err := newDefaultHookAccessor(hooks[0]) + is.NoError(err) + is.Equal("templates/hook.yaml", hookAccessor.Path()) + is.Equal("hook manifest", hookAccessor.Manifest()) +} + +func TestNewDefaultAccessorV2ByValue(t *testing.T) { + // Test that passing v2 release by value also works + is := assert.New(t) + + info := &v2release.Info{Status: common.StatusDeployed, LastDeployed: time.Now()} + rel := v2release.Release{ + Name: "test-release", + Version: 1, + Info: info, + Namespace: "default", + } + + accessor, err := newDefaultAccessor(rel) + is.NoError(err) + is.Equal("test-release", accessor.Name()) +} From 0b134362f442fec44ace35e9cfae6760a6b4e005 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 21:34:16 +0000 Subject: [PATCH 185/571] chore(deps): bump the k8s-io group with 7 updates Bumps the k8s-io group with 7 updates: | Package | From | To | | --- | --- | --- | | [k8s.io/api](https://github.com/kubernetes/api) | `0.35.0` | `0.35.1` | | [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) | `0.35.0` | `0.35.1` | | [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) | `0.35.0` | `0.35.1` | | [k8s.io/apiserver](https://github.com/kubernetes/apiserver) | `0.35.0` | `0.35.1` | | [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime) | `0.35.0` | `0.35.1` | | [k8s.io/client-go](https://github.com/kubernetes/client-go) | `0.35.0` | `0.35.1` | | [k8s.io/kubectl](https://github.com/kubernetes/kubectl) | `0.35.0` | `0.35.1` | Updates `k8s.io/api` from 0.35.0 to 0.35.1 - [Commits](https://github.com/kubernetes/api/compare/v0.35.0...v0.35.1) Updates `k8s.io/apiextensions-apiserver` from 0.35.0 to 0.35.1 - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.35.0...v0.35.1) Updates `k8s.io/apimachinery` from 0.35.0 to 0.35.1 - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.35.0...v0.35.1) Updates `k8s.io/apiserver` from 0.35.0 to 0.35.1 - [Commits](https://github.com/kubernetes/apiserver/compare/v0.35.0...v0.35.1) Updates `k8s.io/cli-runtime` from 0.35.0 to 0.35.1 - [Commits](https://github.com/kubernetes/cli-runtime/compare/v0.35.0...v0.35.1) Updates `k8s.io/client-go` from 0.35.0 to 0.35.1 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.35.0...v0.35.1) Updates `k8s.io/kubectl` from 0.35.0 to 0.35.1 - [Commits](https://github.com/kubernetes/kubectl/compare/v0.35.0...v0.35.1) --- updated-dependencies: - dependency-name: k8s.io/api dependency-version: 0.35.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apiextensions-apiserver dependency-version: 0.35.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apimachinery dependency-version: 0.35.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apiserver dependency-version: 0.35.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/cli-runtime dependency-version: 0.35.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/client-go dependency-version: 0.35.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/kubectl dependency-version: 0.35.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io ... Signed-off-by: dependabot[bot] --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 2a7787076..6ab73f33a 100644 --- a/go.mod +++ b/go.mod @@ -39,14 +39,14 @@ require ( golang.org/x/term v0.40.0 golang.org/x/text v0.34.0 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.35.0 - k8s.io/apiextensions-apiserver v0.35.0 - k8s.io/apimachinery v0.35.0 - k8s.io/apiserver v0.35.0 - k8s.io/cli-runtime v0.35.0 - k8s.io/client-go v0.35.0 + k8s.io/api v0.35.1 + k8s.io/apiextensions-apiserver v0.35.1 + k8s.io/apimachinery v0.35.1 + k8s.io/apiserver v0.35.1 + k8s.io/cli-runtime v0.35.1 + k8s.io/client-go v0.35.1 k8s.io/klog/v2 v2.130.1 - k8s.io/kubectl v0.35.0 + k8s.io/kubectl v0.35.1 oras.land/oras-go/v2 v2.6.0 sigs.k8s.io/controller-runtime v0.23.1 sigs.k8s.io/kustomize/kyaml v0.21.1 @@ -171,7 +171,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/component-base v0.35.0 // indirect + k8s.io/component-base v0.35.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect diff --git a/go.sum b/go.sum index 35b74a320..e512b6147 100644 --- a/go.sum +++ b/go.sum @@ -479,26 +479,26 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY= -k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA= -k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4= -k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU= -k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= -k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -k8s.io/apiserver v0.35.0 h1:CUGo5o+7hW9GcAEF3x3usT3fX4f9r8xmgQeCBDaOgX4= -k8s.io/apiserver v0.35.0/go.mod h1:QUy1U4+PrzbJaM3XGu2tQ7U9A4udRRo5cyxkFX0GEds= -k8s.io/cli-runtime v0.35.0 h1:PEJtYS/Zr4p20PfZSLCbY6YvaoLrfByd6THQzPworUE= -k8s.io/cli-runtime v0.35.0/go.mod h1:VBRvHzosVAoVdP3XwUQn1Oqkvaa8facnokNkD7jOTMY= -k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE= -k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o= -k8s.io/component-base v0.35.0 h1:+yBrOhzri2S1BVqyVSvcM3PtPyx5GUxCK2tinZz1G94= -k8s.io/component-base v0.35.0/go.mod h1:85SCX4UCa6SCFt6p3IKAPej7jSnF3L8EbfSyMZayJR0= +k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q= +k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM= +k8s.io/apiextensions-apiserver v0.35.1 h1:p5vvALkknlOcAqARwjS20kJffgzHqwyQRM8vHLwgU7w= +k8s.io/apiextensions-apiserver v0.35.1/go.mod h1:2CN4fe1GZ3HMe4wBr25qXyJnJyZaquy4nNlNmb3R7AQ= +k8s.io/apimachinery v0.35.1 h1:yxO6gV555P1YV0SANtnTjXYfiivaTPvCTKX6w6qdDsU= +k8s.io/apimachinery v0.35.1/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/apiserver v0.35.1 h1:potxdhhTL4i6AYAa2QCwtlhtB1eCdWQFvJV6fXgJzxs= +k8s.io/apiserver v0.35.1/go.mod h1:BiL6Dd3A2I/0lBnteXfWmCFobHM39vt5+hJQd7Lbpi4= +k8s.io/cli-runtime v0.35.1 h1:uKcXFe8J7AMAM4Gm2JDK4mp198dBEq2nyeYtO+JfGJE= +k8s.io/cli-runtime v0.35.1/go.mod h1:55/hiXIq1C8qIJ3WBrWxEwDLdHQYhBNRdZOz9f7yvTw= +k8s.io/client-go v0.35.1 h1:+eSfZHwuo/I19PaSxqumjqZ9l5XiTEKbIaJ+j1wLcLM= +k8s.io/client-go v0.35.1/go.mod h1:1p1KxDt3a0ruRfc/pG4qT/3oHmUj1AhSHEcxNSGg+OA= +k8s.io/component-base v0.35.1 h1:XgvpRf4srp037QWfGBLFsYMUQJkE5yMa94UsJU7pmcE= +k8s.io/component-base v0.35.1/go.mod h1:HI/6jXlwkiOL5zL9bqA3en1Ygv60F03oEpnuU1G56Bs= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/kubectl v0.35.0 h1:cL/wJKHDe8E8+rP3G7avnymcMg6bH6JEcR5w5uo06wc= -k8s.io/kubectl v0.35.0/go.mod h1:VR5/TSkYyxZwrRwY5I5dDq6l5KXmiCb+9w8IKplk3Qo= +k8s.io/kubectl v0.35.1 h1:zP3Er8C5i1dcAFUMh9Eva0kVvZHptXIn/+8NtRWMxwg= +k8s.io/kubectl v0.35.1/go.mod h1:cQ2uAPs5IO/kx8R5s5J3Ihv3VCYwrx0obCXum0CvnXo= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= From 304d25ffd34fafccbcb81713cb3cfff1be595ae5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:33:24 +0000 Subject: [PATCH 186/571] chore(deps): bump actions/stale from 10.1.1 to 10.2.0 Bumps [actions/stale](https://github.com/actions/stale) from 10.1.1 to 10.2.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/997185467fa4f803885201cee163a9f38240193d...b5d41d4e1d5dceea10e7104786b73624c18a190f) --- updated-dependencies: - dependency-name: actions/stale dependency-version: 10.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/stale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 574427a5d..7d41280ad 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.' From ee018608f6fbf381fac1bae9759164a65c6a0b1f Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Fri, 20 Feb 2026 16:58:32 +0000 Subject: [PATCH 187/571] fix: handle OCI digest algorithm prefix in chart downloader (#31601) * fix: strip digest algorithm prefix before hex decoding OCI references with tag+digest (e.g., chart:1.0@sha256:abc...) failed with "invalid byte" error because the sha256: prefix was passed to hex.DecodeString(). Signed-off-by: Evans Mungai * Add cmd test for OCI references with tag+digest Signed-off-by: Evans Mungai * Move oci registry push result to a struct Signed-off-by: Evans Mungai * Review comments from PR review Signed-off-by: Evans Mungai * Fix failing test Signed-off-by: Evans Mungai --------- Signed-off-by: Evans Mungai --- pkg/chart/common/util/jsonschema.go | 8 ++-- pkg/cmd/pull_test.go | 52 +++++++++++++++++++++++++ pkg/downloader/chart_downloader.go | 22 ++++++++++- pkg/downloader/chart_downloader_test.go | 32 +++++++++++++++ pkg/repo/v1/repotest/server.go | 17 +++++++- 5 files changed, 123 insertions(+), 8 deletions(-) diff --git a/pkg/chart/common/util/jsonschema.go b/pkg/chart/common/util/jsonschema.go index 6d7f32604..873c08fdd 100644 --- a/pkg/chart/common/util/jsonschema.go +++ b/pkg/chart/common/util/jsonschema.go @@ -83,7 +83,7 @@ func ValidateAgainstSchema(ch chart.Charter, values map[string]interface{}) erro slog.Debug("chart name", "chart-name", chrt.Name()) err := ValidateAgainstSingleSchema(values, chrt.Schema()) if err != nil { - sb.WriteString(fmt.Sprintf("%s:\n", chrt.Name())) + fmt.Fprintf(&sb, "%s:\n", chrt.Name()) sb.WriteString(err.Error()) } } @@ -103,10 +103,8 @@ func ValidateAgainstSchema(ch chart.Charter, values map[string]interface{}) erro subchartValues, ok := raw.(map[string]any) if !ok { - sb.WriteString(fmt.Sprintf( - "%s:\ninvalid type for values: expected object (map), got %T\n", - sub.Name(), raw, - )) + fmt.Fprintf(&sb, "%s:\ninvalid type for values: expected object (map), got %T\n", + sub.Name(), raw) continue } diff --git a/pkg/cmd/pull_test.go b/pkg/cmd/pull_test.go index 96631fe05..6d5eb1482 100644 --- a/pkg/cmd/pull_test.go +++ b/pkg/cmd/pull_test.go @@ -22,6 +22,7 @@ import ( "net/http/httptest" "os" "path/filepath" + "strings" "testing" "helm.sh/helm/v4/pkg/repo/v1/repotest" @@ -506,3 +507,54 @@ func TestPullFileCompletion(t *testing.T) { checkFileCompletion(t, "pull", false) checkFileCompletion(t, "pull repo/chart", false) } + +// TestPullOCIWithTagAndDigest tests pulling an OCI chart with both tag and digest specified. +// This is a regression test for https://github.com/helm/helm/issues/31600 +func TestPullOCIWithTagAndDigest(t *testing.T) { + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testcharts/*.tgz*"), + ) + defer srv.Stop() + + ociSrv, err := repotest.NewOCIServer(t, srv.Root()) + if err != nil { + t.Fatal(err) + } + result := ociSrv.RunWithReturn(t) + + contentCache := t.TempDir() + outdir := t.TempDir() + + // Test: pull with tag and digest (the fixed bug from issue #31600) + // Previously this failed with "encoding/hex: invalid byte: U+0073 's'" + ref := fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.1.0@%s", + ociSrv.RegistryURL, result.PushedChart.Manifest.Digest) + + cmd := fmt.Sprintf("pull %s -d '%s' --registry-config %s --content-cache %s --plain-http", + ref, + outdir, + filepath.Join(srv.Root(), "config.json"), + contentCache, + ) + + _, _, err = executeActionCommand(cmd) + if err != nil { + t.Fatalf("pull with tag+digest failed: %v", err) + } + + // Verify the file was downloaded + // When digest is present, the filename uses the digest format (e.g. chart@sha256-hex.tgz) + expectedFile := filepath.Join(outdir, "oci-dependent-chart-0.1.0.tgz") + if _, err := os.Stat(expectedFile); err != nil { + // Try the digest-based filename; parse algorithm:hex to avoid fixed-offset assumptions + algorithm, digestPart, ok := strings.Cut(result.PushedChart.Manifest.Digest, ":") + if !ok { + t.Fatalf("digest must be in algorithm:hex format, got %q", result.PushedChart.Manifest.Digest) + } + expectedFile = filepath.Join(outdir, fmt.Sprintf("oci-dependent-chart@%s-%s.tgz", algorithm, digestPart)) + if _, err := os.Stat(expectedFile); err != nil { + t.Errorf("expected chart file not found: %v", err) + } + } +} diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 522233a7d..d21a79335 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -125,10 +125,15 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven var digest32 [32]byte if hash != "" { // if there is a hash, populate the other formats - digest, err = hex.DecodeString(hash) + // Strip the algorithm prefix (e.g., "sha256:") if present + digest, err = hex.DecodeString(stripDigestAlgorithm(hash)) if err != nil { return "", nil, err } + if len(digest) != 32 { + return "", nil, fmt.Errorf("invalid digest length: %d", len(digest)) + } + copy(digest32[:], digest) if pth, err := c.Cache.Get(digest32, CacheChart); err == nil { fdata, err := os.ReadFile(pth) @@ -231,10 +236,14 @@ func (c *ChartDownloader) DownloadToCache(ref, version string) (string, *provena c.Options = append(c.Options, getter.WithAcceptHeader("application/gzip,application/octet-stream")) // Check the cache for the file - digest, err := hex.DecodeString(digestString) + // Strip the algorithm prefix (e.g., "sha256:") if present + digest, err := hex.DecodeString(stripDigestAlgorithm(digestString)) if err != nil { return "", nil, fmt.Errorf("unable to decode digest: %w", err) } + if digestString != "" && len(digest) != 32 { + return "", nil, fmt.Errorf("invalid digest length: %d", len(digest)) + } var digest32 [32]byte copy(digest32[:], digest) @@ -584,3 +593,12 @@ func loadRepoConfig(file string) (*repo.File, error) { } return r, nil } + +// stripDigestAlgorithm removes the algorithm prefix (e.g., "sha256:") from a digest string. +// If no prefix is present, the original string is returned unchanged. +func stripDigestAlgorithm(digest string) string { + if idx := strings.Index(digest, ":"); idx >= 0 { + return digest[idx+1:] + } + return digest +} diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index 777e21a6e..8a7514a8e 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -23,6 +23,7 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "helm.sh/helm/v4/internal/test/ensure" @@ -486,3 +487,34 @@ func TestDownloadToCache(t *testing.T) { c.Keyring = "" }) } + +func TestStripDigestAlgorithm(t *testing.T) { + tests := map[string]struct { + input string + expected string + }{ + "sha256 prefixed digest": { + input: "sha256:aef46c66a7f2d5a12a7e3f54a64790daf5c9a9e66af3f46955efdaa6c900341d", + expected: "aef46c66a7f2d5a12a7e3f54a64790daf5c9a9e66af3f46955efdaa6c900341d", + }, + "sha512 prefixed digest": { + input: "sha512:abcdef1234567890", + expected: "abcdef1234567890", + }, + "plain hex digest without prefix": { + input: "aef46c66a7f2d5a12a7e3f54a64790daf5c9a9e66af3f46955efdaa6c900341d", + expected: "aef46c66a7f2d5a12a7e3f54a64790daf5c9a9e66af3f46955efdaa6c900341d", + }, + "empty string": { + input: "", + expected: "", + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + result := stripDigestAlgorithm(tt.input) + assert.Equalf(t, tt.expected, result, "stripDigestAlgorithm(%q) = %q, want %q", tt.input, result, tt.expected) + }) + } +} diff --git a/pkg/repo/v1/repotest/server.go b/pkg/repo/v1/repotest/server.go index 12b96de5a..22b4c8060 100644 --- a/pkg/repo/v1/repotest/server.go +++ b/pkg/repo/v1/repotest/server.go @@ -153,6 +153,10 @@ type OCIServerRunConfig struct { type OCIServerOpt func(config *OCIServerRunConfig) +type OCIServerRunResult struct { + PushedChart *ociRegistry.PushResult +} + func WithDependingChart(c *chart.Chart) OCIServerOpt { return func(config *OCIServerRunConfig) { config.DependingChart = c @@ -210,6 +214,11 @@ func NewOCIServer(t *testing.T, dir string) (*OCIServer, error) { } func (srv *OCIServer) Run(t *testing.T, opts ...OCIServerOpt) { + t.Helper() + _ = srv.RunWithReturn(t, opts...) +} + +func (srv *OCIServer) RunWithReturn(t *testing.T, opts ...OCIServerOpt) *OCIServerRunResult { t.Helper() cfg := &OCIServerRunConfig{} for _, fn := range opts { @@ -284,7 +293,9 @@ func (srv *OCIServer) Run(t *testing.T, opts ...OCIServerOpt) { srv.Client = registryClient c := cfg.DependingChart if c == nil { - return + return &OCIServerRunResult{ + PushedChart: result, + } } dependingRef := fmt.Sprintf("%s/u/ocitestuser/%s:%s", @@ -308,6 +319,10 @@ func (srv *OCIServer) Run(t *testing.T, opts ...OCIServerOpt) { result.Manifest.Digest, result.Manifest.Size, result.Config.Digest, result.Config.Size, result.Chart.Digest, result.Chart.Size) + + return &OCIServerRunResult{ + PushedChart: result, + } } // Root gets the docroot for the server. From c59c140ce07ce973f16fe50c0c5e991e1d6308a6 Mon Sep 17 00:00:00 2001 From: rohansood10 Date: Sat, 21 Feb 2026 09:32:44 -0800 Subject: [PATCH 188/571] fix: correct import comment in statuswait.go from v3 to v4 The import comment in pkg/kube/statuswait.go still referenced helm.sh/helm/v3/pkg/kube while all other files in the package correctly reference helm.sh/helm/v4/pkg/kube. This mismatch causes downstream processing errors (e.g. kythe) when vendoring Helm. Fixes #31846 Signed-off-by: rohansood10 --- pkg/kube/statuswait.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index 01024afa6..28ee84bc2 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v3/pkg/kube" +package kube // import "helm.sh/helm/v4/pkg/kube" import ( "context" From 92b64e87ad6245d64d5b49bbbbf8dead83faac22 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Mon, 23 Feb 2026 16:46:35 -0800 Subject: [PATCH 189/571] chore: fixes Signed-off-by: George Jenkins --- internal/chart/v3/chart.go | 2 +- internal/chart/v3/util/dependencies_test.go | 2 +- internal/chart/v3/util/save_test.go | 2 +- internal/release/v2/hook.go | 4 +- internal/release/v2/hook_test.go | 2 +- internal/release/v2/info.go | 2 +- internal/release/v2/info_test.go | 2 +- internal/release/v2/mock.go | 2 +- internal/release/v2/release.go | 2 +- internal/release/v2/util/kind_sorter.go | 2 +- pkg/action/action.go | 2 +- pkg/action/action_test.go | 26 +++---- pkg/action/get_values.go | 2 +- pkg/action/get_values_test.go | 32 ++++----- pkg/action/hooks_test.go | 4 +- pkg/action/install_test.go | 60 ++++++++-------- pkg/action/lint.go | 4 +- pkg/action/lint_test.go | 4 +- pkg/action/package.go | 2 +- pkg/action/show_test.go | 2 +- pkg/action/upgrade_test.go | 56 +++++++-------- pkg/chart/common/util/jsonschema.go | 2 +- pkg/chart/common/values.go | 18 ++--- pkg/chart/common/values_test.go | 4 +- pkg/chart/v2/chart.go | 4 +- pkg/chart/v2/dependency.go | 2 +- pkg/chart/v2/errors.go | 2 +- pkg/chart/v2/lint/lint.go | 2 +- pkg/chart/v2/lint/rules/template_test.go | 4 +- pkg/chart/v2/loader/load_test.go | 24 +++---- pkg/chart/v2/util/dependencies.go | 28 ++++---- pkg/chart/v2/util/dependencies_test.go | 2 +- pkg/chart/v2/util/save_test.go | 2 +- pkg/cli/output/output.go | 4 +- pkg/cli/values/options_test.go | 20 +++--- pkg/cmd/create_test.go | 9 +-- pkg/cmd/get_all.go | 2 +- pkg/cmd/get_values.go | 2 +- pkg/cmd/history.go | 2 +- pkg/cmd/history_test.go | 2 +- pkg/cmd/printer.go | 2 +- pkg/engine/engine.go | 30 ++++---- pkg/engine/engine_test.go | 62 ++++++++--------- pkg/engine/funcs.go | 46 ++++++------- pkg/engine/funcs_test.go | 44 ++++++------ pkg/kube/statuswait_test.go | 2 +- pkg/kube/wait_test.go | 2 +- pkg/provenance/sign.go | 2 +- pkg/registry/registry_test.go | 2 +- pkg/release/interfaces.go | 4 +- pkg/release/v1/hook.go | 4 +- pkg/release/v1/hook_test.go | 2 +- pkg/release/v1/info.go | 2 +- pkg/release/v1/info_test.go | 2 +- pkg/release/v1/mock.go | 2 +- pkg/release/v1/release.go | 2 +- pkg/release/v1/util/kind_sorter.go | 2 +- pkg/repo/v1/index.go | 6 +- pkg/repo/v1/repotest/server.go | 2 +- pkg/storage/storage_test.go | 2 +- pkg/strvals/literal_parser.go | 34 ++++----- pkg/strvals/parser.go | 76 ++++++++++----------- 62 files changed, 339 insertions(+), 344 deletions(-) diff --git a/internal/chart/v3/chart.go b/internal/chart/v3/chart.go index 217594df9..d76a5688c 100644 --- a/internal/chart/v3/chart.go +++ b/internal/chart/v3/chart.go @@ -49,7 +49,7 @@ type Chart struct { // Schema is an optional JSON schema for imposing structure on Values Schema []byte `json:"schema"` // SchemaModTime the schema was last modified - SchemaModTime time.Time `json:"schemamodtime,omitempty"` + SchemaModTime time.Time `json:"schemamodtime"` // Files are miscellaneous files in a chart archive, // e.g. README, LICENSE, etc. Files []*common.File `json:"files"` diff --git a/internal/chart/v3/util/dependencies_test.go b/internal/chart/v3/util/dependencies_test.go index 3c5bb96f7..82c6ee8fc 100644 --- a/internal/chart/v3/util/dependencies_test.go +++ b/internal/chart/v3/util/dependencies_test.go @@ -63,7 +63,7 @@ func TestLoadDependency(t *testing.T) { } func TestDependencyEnabled(t *testing.T) { - type M = map[string]interface{} + type M = map[string]any tests := []struct { name string v M diff --git a/internal/chart/v3/util/save_test.go b/internal/chart/v3/util/save_test.go index 7a42a76af..1950faa22 100644 --- a/internal/chart/v3/util/save_test.go +++ b/internal/chart/v3/util/save_test.go @@ -153,7 +153,7 @@ func TestSavePreservesTimestamps(t *testing.T) { Version: "1.2.3", }, ModTime: initialCreateTime, - Values: map[string]interface{}{ + Values: map[string]any{ "imageName": "testimage", "imageId": 42, }, diff --git a/internal/release/v2/hook.go b/internal/release/v2/hook.go index 255b6ed01..5009ffbd0 100644 --- a/internal/release/v2/hook.go +++ b/internal/release/v2/hook.go @@ -86,7 +86,7 @@ type Hook struct { // Events are the events that this hook fires on. Events []HookEvent `json:"events,omitempty"` // LastRun indicates the date/time this was last run. - LastRun HookExecution `json:"last_run,omitempty"` + LastRun HookExecution `json:"last_run"` // Weight indicates the sort order for execution among similar Hook type Weight int `json:"weight,omitempty"` // DeletePolicies are the policies that indicate when to delete the hook @@ -133,7 +133,7 @@ type hookExecutionJSON struct { // It handles empty string time fields by treating them as zero values. func (h *HookExecution) UnmarshalJSON(data []byte) error { // First try to unmarshal into a map to handle empty string time fields - var raw map[string]interface{} + var raw map[string]any if err := json.Unmarshal(data, &raw); err != nil { return err } diff --git a/internal/release/v2/hook_test.go b/internal/release/v2/hook_test.go index c3e2e0261..5a0867398 100644 --- a/internal/release/v2/hook_test.go +++ b/internal/release/v2/hook_test.go @@ -220,7 +220,7 @@ func TestHookExecutionEmptyStringRoundTrip(t *testing.T) { data, err := json.Marshal(&exec) require.NoError(t, err) - var result map[string]interface{} + var result map[string]any err = json.Unmarshal(data, &result) require.NoError(t, err) diff --git a/internal/release/v2/info.go b/internal/release/v2/info.go index 038f19409..6b17c0edd 100644 --- a/internal/release/v2/info.go +++ b/internal/release/v2/info.go @@ -57,7 +57,7 @@ type infoJSON struct { // It handles empty string time fields by treating them as zero values. func (i *Info) UnmarshalJSON(data []byte) error { // First try to unmarshal into a map to handle empty string time fields - var raw map[string]interface{} + var raw map[string]any if err := json.Unmarshal(data, &raw); err != nil { return err } diff --git a/internal/release/v2/info_test.go b/internal/release/v2/info_test.go index 32babb82f..560861e06 100644 --- a/internal/release/v2/info_test.go +++ b/internal/release/v2/info_test.go @@ -272,7 +272,7 @@ func TestInfoEmptyStringRoundTrip(t *testing.T) { data, err := json.Marshal(&info) require.NoError(t, err) - var result map[string]interface{} + var result map[string]any err = json.Unmarshal(data, &result) require.NoError(t, err) diff --git a/internal/release/v2/mock.go b/internal/release/v2/mock.go index b1b06cade..8a86d5edc 100644 --- a/internal/release/v2/mock.go +++ b/internal/release/v2/mock.go @@ -124,7 +124,7 @@ func Mock(opts *MockReleaseOptions) *Release { Name: name, Info: info, Chart: ch, - Config: map[string]interface{}{"name": "value"}, + Config: map[string]any{"name": "value"}, Version: version, Namespace: namespace, Hooks: []*Hook{ diff --git a/internal/release/v2/release.go b/internal/release/v2/release.go index 85ffb0b2f..8b8f2ee07 100644 --- a/internal/release/v2/release.go +++ b/internal/release/v2/release.go @@ -36,7 +36,7 @@ type Release struct { Chart *chart.Chart `json:"chart,omitempty"` // Config is the set of extra Values added to the chart. // These values override the default values inside of the chart. - Config map[string]interface{} `json:"config,omitempty"` + Config map[string]any `json:"config,omitempty"` // Manifest is the string representation of the rendered template. Manifest string `json:"manifest,omitempty"` // Hooks are all of the hooks declared for this release. diff --git a/internal/release/v2/util/kind_sorter.go b/internal/release/v2/util/kind_sorter.go index 71bacb228..dba35b6d2 100644 --- a/internal/release/v2/util/kind_sorter.go +++ b/internal/release/v2/util/kind_sorter.go @@ -137,7 +137,7 @@ func sortHooksByKind(hooks []*release.Hook, ordering KindSortOrder) []*release.H return h } -func lessByKind(_ interface{}, _ interface{}, kindA string, kindB string, o KindSortOrder) bool { +func lessByKind(_ any, _ any, kindA string, kindB string, o KindSortOrder) bool { ordering := make(map[string]int, len(o)) for v, k := range o { ordering[k] = v diff --git a/pkg/action/action.go b/pkg/action/action.go index c2a27940f..49d7316c0 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -466,7 +466,7 @@ func GetVersionSet(client discovery.ServerResourcesInterface) (common.VersionSet return common.DefaultVersionSet, nil } - versionMap := make(map[string]interface{}) + versionMap := make(map[string]any) var versions []string // Extract the groups diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index 85ee42d64..29a4885bd 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -157,12 +157,12 @@ func withName(name string) chartOption { } func withSampleValues() chartOption { - values := map[string]interface{}{ + values := map[string]any{ "someKey": "someValue", - "nestedKey": map[string]interface{}{ + "nestedKey": map[string]any{ "simpleKey": "simpleValue", - "anotherNestedKey": map[string]interface{}{ - "yetAnotherNestedKey": map[string]interface{}{ + "anotherNestedKey": map[string]any{ + "yetAnotherNestedKey": map[string]any{ "youReadyForAnotherNestedKey": "No", }, }, @@ -173,7 +173,7 @@ func withSampleValues() chartOption { } } -func withValues(values map[string]interface{}) chartOption { +func withValues(values map[string]any) chartOption { return func(opts *chartOptions) { opts.Values = values } @@ -274,7 +274,7 @@ func namedReleaseStub(name string, status rcommon.Status) *release.Release { Description: "Named Release Stub", }, Chart: buildChart(withSampleTemplates()), - Config: map[string]interface{}{"name": "value"}, + Config: map[string]any{"name": "value"}, Version: 1, Hooks: []*release.Hook{ { @@ -304,7 +304,7 @@ func TestConfiguration_Init(t *testing.T) { tests := []struct { name string helmDriver string - expectedDriverType interface{} + expectedDriverType any expectErr bool errMsg string }{ @@ -795,7 +795,7 @@ func TestRenderResources_PostRenderer_Success(t *testing.T) { } ch := buildChart(withSampleTemplates()) - values := map[string]interface{}{} + values := map[string]any{} hooks, buf, notes, err := cfg.renderResources( ch, values, "test-release", "", false, false, false, @@ -838,7 +838,7 @@ func TestRenderResources_PostRenderer_Error(t *testing.T) { } ch := buildChart(withSampleTemplates()) - values := map[string]interface{}{} + values := map[string]any{} _, _, _, err := cfg.renderResources( ch, values, "test-release", "", false, false, false, @@ -866,7 +866,7 @@ func TestRenderResources_PostRenderer_MergeError(t *testing.T) { {Name: "templates/invalid", ModTime: time.Now(), Data: []byte("invalid: yaml: content:")}, }, } - values := map[string]interface{}{} + values := map[string]any{} _, _, _, err := cfg.renderResources( ch, values, "test-release", "", false, false, false, @@ -888,7 +888,7 @@ func TestRenderResources_PostRenderer_SplitError(t *testing.T) { } ch := buildChart(withSampleTemplates()) - values := map[string]interface{}{} + values := map[string]any{} _, _, _, err := cfg.renderResources( ch, values, "test-release", "", false, false, false, @@ -909,7 +909,7 @@ func TestRenderResources_PostRenderer_Integration(t *testing.T) { } ch := buildChart(withSampleTemplates()) - values := map[string]interface{}{} + values := map[string]any{} hooks, buf, notes, err := cfg.renderResources( ch, values, "test-release", "", false, false, false, @@ -945,7 +945,7 @@ func TestRenderResources_NoPostRenderer(t *testing.T) { cfg := actionConfigFixture(t) ch := buildChart(withSampleTemplates()) - values := map[string]interface{}{} + values := map[string]any{} hooks, buf, notes, err := cfg.renderResources( ch, values, "test-release", "", false, false, false, diff --git a/pkg/action/get_values.go b/pkg/action/get_values.go index 6475a140b..4a5e6f589 100644 --- a/pkg/action/get_values.go +++ b/pkg/action/get_values.go @@ -42,7 +42,7 @@ func NewGetValues(cfg *Configuration) *GetValues { } // Run executes 'helm get values' against the given release. -func (g *GetValues) Run(name string) (map[string]interface{}, error) { +func (g *GetValues) Run(name string) (map[string]any, error) { if err := g.cfg.KubeClient.IsReachable(); err != nil { return nil, err } diff --git a/pkg/action/get_values_test.go b/pkg/action/get_values_test.go index 69a95a2e4..01ee4c3f1 100644 --- a/pkg/action/get_values_test.go +++ b/pkg/action/get_values_test.go @@ -45,12 +45,12 @@ func TestGetValues_Run_UserConfigOnly(t *testing.T) { client := NewGetValues(cfg) releaseName := "test-release" - userConfig := map[string]interface{}{ - "database": map[string]interface{}{ + userConfig := map[string]any{ + "database": map[string]any{ "host": "localhost", "port": 5432, }, - "app": map[string]interface{}{ + "app": map[string]any{ "name": "my-app", "replicas": 3, }, @@ -66,9 +66,9 @@ func TestGetValues_Run_UserConfigOnly(t *testing.T) { Name: "test-chart", Version: "1.0.0", }, - Values: map[string]interface{}{ + Values: map[string]any{ "defaultKey": "defaultValue", - "app": map[string]interface{}{ + "app": map[string]any{ "name": "default-app", "timeout": 30, }, @@ -92,19 +92,19 @@ func TestGetValues_Run_AllValues(t *testing.T) { client.AllValues = true releaseName := "test-release" - userConfig := map[string]interface{}{ - "database": map[string]interface{}{ + userConfig := map[string]any{ + "database": map[string]any{ "host": "localhost", "port": 5432, }, - "app": map[string]interface{}{ + "app": map[string]any{ "name": "my-app", }, } - chartDefaultValues := map[string]interface{}{ + chartDefaultValues := map[string]any{ "defaultKey": "defaultValue", - "app": map[string]interface{}{ + "app": map[string]any{ "name": "default-app", "timeout": 30, }, @@ -132,11 +132,11 @@ func TestGetValues_Run_AllValues(t *testing.T) { result, err := client.Run(releaseName) require.NoError(t, err) - assert.Equal(t, "my-app", result["app"].(map[string]interface{})["name"]) - assert.Equal(t, 30, result["app"].(map[string]interface{})["timeout"]) + assert.Equal(t, "my-app", result["app"].(map[string]any)["name"]) + assert.Equal(t, 30, result["app"].(map[string]any)["timeout"]) assert.Equal(t, "defaultValue", result["defaultKey"]) - assert.Equal(t, "localhost", result["database"].(map[string]interface{})["host"]) - assert.Equal(t, 5432, result["database"].(map[string]interface{})["port"]) + assert.Equal(t, "localhost", result["database"].(map[string]any)["host"]) + assert.Equal(t, 5432, result["database"].(map[string]any)["port"]) } func TestGetValues_Run_EmptyValues(t *testing.T) { @@ -156,7 +156,7 @@ func TestGetValues_Run_EmptyValues(t *testing.T) { Version: "1.0.0", }, }, - Config: map[string]interface{}{}, + Config: map[string]any{}, Version: 1, Namespace: "default", } @@ -165,7 +165,7 @@ func TestGetValues_Run_EmptyValues(t *testing.T) { result, err := client.Run(releaseName) require.NoError(t, err) - assert.Equal(t, map[string]interface{}{}, result) + assert.Equal(t, map[string]any{}, result) } func TestGetValues_Run_UnreachableKubeClient(t *testing.T) { diff --git a/pkg/action/hooks_test.go b/pkg/action/hooks_test.go index 0270a0630..cb529b125 100644 --- a/pkg/action/hooks_test.go +++ b/pkg/action/hooks_test.go @@ -186,7 +186,7 @@ func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace str {Name: "templates/hello", ModTime: modTime, Data: []byte("hello: world")}, {Name: "templates/hooks", ModTime: modTime, Data: []byte(manifest)}, } - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChartWithTemplates(templates), vals) is.NoError(err) @@ -216,7 +216,7 @@ func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace str {Name: "templates/hello", ModTime: modTime, Data: []byte("hello: world")}, {Name: "templates/hooks", ModTime: modTime, Data: []byte(manifest)}, } - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChartWithTemplates(templates), vals) is.Error(err) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 52dd83788..a664a3473 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -182,7 +182,7 @@ func TestInstallRelease(t *testing.T) { req := require.New(t) instAction := installAction(t) - vals := map[string]interface{}{} + vals := map[string]any{} ctx, done := context.WithCancel(t.Context()) resi, err := instAction.RunWithContext(ctx, buildChart(), vals) if err != nil { @@ -288,13 +288,13 @@ func TestInstallReleaseWithTakeOwnership_ResourceOwnedNoFlag(t *testing.T) { func TestInstallReleaseWithValues(t *testing.T) { is := assert.New(t) instAction := installAction(t) - userVals := map[string]interface{}{ - "nestedKey": map[string]interface{}{ + userVals := map[string]any{ + "nestedKey": map[string]any{ "simpleKey": "simpleValue", }, } - expectedUserValues := map[string]interface{}{ - "nestedKey": map[string]interface{}{ + expectedUserValues := map[string]any{ + "nestedKey": map[string]any{ "simpleKey": "simpleValue", }, } @@ -328,7 +328,7 @@ func TestInstallReleaseWithValues(t *testing.T) { func TestInstallRelease_NoName(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "" - vals := map[string]interface{}{} + vals := map[string]any{} _, err := instAction.Run(buildChart(), vals) if err == nil { t.Fatal("expected failure when no name is specified") @@ -340,7 +340,7 @@ func TestInstallRelease_WithNotes(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.ReleaseName = "with-notes" - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(withNotes("note here")), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -371,7 +371,7 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.ReleaseName = "with-notes" - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -394,7 +394,7 @@ func TestInstallRelease_WithChartAndDependencyParentNotes(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.ReleaseName = "with-notes" - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -417,7 +417,7 @@ func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "with-notes" instAction.SubNotes = true - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -443,7 +443,7 @@ func TestInstallRelease_DryRunClient(t *testing.T) { instAction := installAction(t) instAction.DryRunStrategy = dryRunStrategy - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(withSampleTemplates()), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -471,7 +471,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { // First perform a normal dry-run with the secret and confirm its presence. instAction.DryRunStrategy = DryRunClient - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -486,7 +486,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { // Perform a dry-run where the secret should not be present instAction.HideSecret = true - vals = map[string]interface{}{} + vals = map[string]any{} res2i, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -502,7 +502,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { // Ensure there is an error when HideSecret True but not in a dry-run mode instAction.DryRunStrategy = DryRunNone - vals = map[string]interface{}{} + vals = map[string]any{} _, err = instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) if err == nil { t.Fatalf("Did not get expected an error when dry-run false and hide secret is true") @@ -514,7 +514,7 @@ func TestInstallRelease_DryRun_Lookup(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.DryRunStrategy = DryRunNone - vals := map[string]interface{}{} + vals := map[string]any{} mockChart := buildChart(withSampleTemplates()) mockChart.Templates = append(mockChart.Templates, &common.File{ @@ -537,7 +537,7 @@ func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.DryRunStrategy = DryRunNone - vals := map[string]interface{}{} + vals := map[string]any{} _, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals) expectedErr := `hello/templates/incorrect:1:10 executing "hello/templates/incorrect" at <.Values.bad.doh>: @@ -555,7 +555,7 @@ func TestInstallRelease_NoHooks(t *testing.T) { instAction.ReleaseName = "no-hooks" require.NoError(t, instAction.cfg.Releases.Create(releaseStub())) - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(), vals) if err != nil { t.Fatalf("Failed install: %s", err) @@ -576,7 +576,7 @@ func TestInstallRelease_FailedHooks(t *testing.T) { outBuffer := &bytes.Buffer{} failer.PrintingKubeClient = kubefake.PrintingKubeClient{Out: io.Discard, LogOutput: outBuffer} - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(), vals) is.Error(err) res, err := releaserToV1Release(resi) @@ -596,7 +596,7 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) { require.NoError(t, instAction.cfg.Releases.Create(rel)) instAction.ReleaseName = rel.Name - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(), vals) is.NoError(err) res, err := releaserToV1Release(resi) @@ -616,13 +616,13 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) { func TestInstallRelease_KubeVersion(t *testing.T) { is := assert.New(t) instAction := installAction(t) - vals := map[string]interface{}{} + vals := map[string]any{} _, err := instAction.Run(buildChart(withKube(">=0.0.0")), vals) is.NoError(err) // This should fail for a few hundred years instAction.ReleaseName = "should-fail" - vals = map[string]interface{}{} + vals = map[string]any{} _, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals) is.Error(err) is.Contains(err.Error(), "chart requires kubeVersion: >=99.0.0 which is incompatible with Kubernetes v1.20.") @@ -636,7 +636,7 @@ func TestInstallRelease_Wait(t *testing.T) { failer.WaitError = fmt.Errorf("I timed out") instAction.cfg.KubeClient = failer instAction.WaitStrategy = kube.StatusWatcherStrategy - vals := map[string]interface{}{} + vals := map[string]any{} goroutines := instAction.getGoroutineCount() @@ -657,7 +657,7 @@ func TestInstallRelease_Wait_Interrupted(t *testing.T) { failer.WaitDuration = 10 * time.Second instAction.cfg.KubeClient = failer instAction.WaitStrategy = kube.StatusWatcherStrategy - vals := map[string]interface{}{} + vals := map[string]any{} ctx, cancel := context.WithCancel(t.Context()) time.AfterFunc(time.Second, cancel) @@ -681,7 +681,7 @@ func TestInstallRelease_WaitForJobs(t *testing.T) { instAction.cfg.KubeClient = failer instAction.WaitStrategy = kube.StatusWatcherStrategy instAction.WaitForJobs = true - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(), vals) is.Error(err) @@ -704,7 +704,7 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) { // disabling hooks to avoid an early fail when // WaitForDelete is called on the pre-delete hook execution instAction.DisableHooks = true - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := instAction.Run(buildChart(), vals) is.Error(err) @@ -727,7 +727,7 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) { failer.DeleteError = fmt.Errorf("uninstall fail") instAction.cfg.KubeClient = failer instAction.RollbackOnFailure = true - vals := map[string]interface{}{} + vals := map[string]any{} _, err := instAction.Run(buildChart(), vals) is.Error(err) @@ -745,7 +745,7 @@ func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { failer.WaitDuration = 10 * time.Second instAction.cfg.KubeClient = failer instAction.RollbackOnFailure = true - vals := map[string]interface{}{} + vals := map[string]any{} ctx, cancel := context.WithCancel(t.Context()) time.AfterFunc(time.Second, cancel) @@ -841,7 +841,7 @@ func TestNameTemplate(t *testing.T) { func TestInstallReleaseOutputDir(t *testing.T) { is := assert.New(t) instAction := installAction(t) - vals := map[string]interface{}{} + vals := map[string]any{} dir := t.TempDir() @@ -873,7 +873,7 @@ func TestInstallReleaseOutputDir(t *testing.T) { func TestInstallOutputDirWithReleaseName(t *testing.T) { is := assert.New(t) instAction := installAction(t) - vals := map[string]interface{}{} + vals := map[string]any{} dir := t.TempDir() @@ -1290,7 +1290,7 @@ func TestInstallRelease_WaitOptionsPassedDownstream(t *testing.T) { // Access the underlying FailingKubeClient to check recorded options failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - vals := map[string]interface{}{} + vals := map[string]any{} _, err := instAction.Run(buildChart(), vals) is.NoError(err) diff --git a/pkg/action/lint.go b/pkg/action/lint.go index 208fd4637..6156fe5c8 100644 --- a/pkg/action/lint.go +++ b/pkg/action/lint.go @@ -53,7 +53,7 @@ func NewLint() *Lint { } // Run executes 'helm Lint' against the given chart. -func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult { +func (l *Lint) Run(paths []string, vals map[string]any) *LintResult { lowestTolerance := support.ErrorSev if l.Strict { lowestTolerance = support.WarningSev @@ -87,7 +87,7 @@ func HasWarningsOrErrors(result *LintResult) bool { return len(result.Errors) > 0 } -func lintChart(path string, vals map[string]interface{}, namespace string, kubeVersion *common.KubeVersion, skipSchemaValidation bool) (support.Linter, error) { +func lintChart(path string, vals map[string]any, namespace string, kubeVersion *common.KubeVersion, skipSchemaValidation bool) (support.Linter, error) { var chartPath string linter := support.Linter{} diff --git a/pkg/action/lint_test.go b/pkg/action/lint_test.go index 4684f91f1..6ee1e07fa 100644 --- a/pkg/action/lint_test.go +++ b/pkg/action/lint_test.go @@ -26,7 +26,7 @@ import ( ) var ( - values = make(map[string]interface{}) + values = make(map[string]any) namespace = "testNamespace" chart1MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-1" chart2MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-2" @@ -88,7 +88,7 @@ func TestLintChart(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := lintChart(tt.chartPath, map[string]interface{}{}, namespace, nil, tt.skipSchemaValidation) + _, err := lintChart(tt.chartPath, map[string]any{}, namespace, nil, tt.skipSchemaValidation) switch { case err != nil && !tt.err: t.Errorf("%s", err) diff --git a/pkg/action/package.go b/pkg/action/package.go index 0ab49538c..86426b412 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -70,7 +70,7 @@ func NewPackage() *Package { } // Run executes 'helm package' against the given chart and returns the path to the packaged chart. -func (p *Package) Run(path string, _ map[string]interface{}) (string, error) { +func (p *Package) Run(path string, _ map[string]any) (string, error) { chrt, err := loader.LoadDir(path) if err != nil { return "", err diff --git a/pkg/action/show_test.go b/pkg/action/show_test.go index 6e270ac6d..854dee07a 100644 --- a/pkg/action/show_test.go +++ b/pkg/action/show_test.go @@ -43,7 +43,7 @@ func TestShow(t *testing.T) { Raw: []*common.File{ {Name: "values.yaml", ModTime: modTime, Data: []byte("VALUES\n")}, }, - Values: map[string]interface{}{}, + Values: map[string]any{}, } output, err := client.Run("") diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 848e8a682..f4606a3e9 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -60,7 +60,7 @@ func TestUpgradeRelease_Success(t *testing.T) { req.NoError(upAction.cfg.Releases.Create(rel)) upAction.WaitStrategy = kube.StatusWatcherStrategy - vals := map[string]interface{}{} + vals := map[string]any{} ctx, done := context.WithCancel(t.Context()) resi, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) @@ -94,7 +94,7 @@ func TestUpgradeRelease_Wait(t *testing.T) { failer.WaitError = fmt.Errorf("I timed out") upAction.cfg.KubeClient = failer upAction.WaitStrategy = kube.StatusWatcherStrategy - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) @@ -119,7 +119,7 @@ func TestUpgradeRelease_WaitForJobs(t *testing.T) { upAction.cfg.KubeClient = failer upAction.WaitStrategy = kube.StatusWatcherStrategy upAction.WaitForJobs = true - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) @@ -145,7 +145,7 @@ func TestUpgradeRelease_CleanupOnFail(t *testing.T) { upAction.cfg.KubeClient = failer upAction.WaitStrategy = kube.StatusWatcherStrategy upAction.CleanupOnFail = true - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) @@ -173,7 +173,7 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) { failer.WatchUntilReadyError = fmt.Errorf("arming key removed") upAction.cfg.KubeClient = failer upAction.RollbackOnFailure = true - vals := map[string]interface{}{} + vals := map[string]any{} resi, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) @@ -202,7 +202,7 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) { failer.UpdateError = fmt.Errorf("update fail") upAction.cfg.KubeClient = failer upAction.RollbackOnFailure = true - vals := map[string]interface{}{} + vals := map[string]any{} _, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) @@ -217,17 +217,17 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { t.Run("reuse values should work with values", func(t *testing.T) { upAction := upgradeAction(t) - existingValues := map[string]interface{}{ + existingValues := map[string]any{ "name": "value", "maxHeapSize": "128m", "replicas": 2, } - newValues := map[string]interface{}{ + newValues := map[string]any{ "name": "newValue", "maxHeapSize": "512m", "cpu": "12m", } - expectedValues := map[string]interface{}{ + expectedValues := map[string]any{ "name": "newValue", "maxHeapSize": "512m", "cpu": "12m", @@ -266,8 +266,8 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { t.Run("reuse values should not install disabled charts", func(t *testing.T) { upAction := upgradeAction(t) - chartDefaultValues := map[string]interface{}{ - "subchart": map[string]interface{}{ + chartDefaultValues := map[string]any{ + "subchart": map[string]any{ "enabled": true, }, } @@ -283,8 +283,8 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { withMetadataDependency(dependency), ) now := time.Now() - existingValues := map[string]interface{}{ - "subchart": map[string]interface{}{ + existingValues := map[string]any{ + "subchart": map[string]any{ "enabled": false, }, } @@ -311,7 +311,7 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { withMetadataDependency(dependency), ) // reusing values and upgrading - resi, err := upAction.Run(rel.Name, sampleChartWithSubChart, map[string]interface{}{}) + resi, err := upAction.Run(rel.Name, sampleChartWithSubChart, map[string]any{}) is.NoError(err) res, err := releaserToV1Release(resi) is.NoError(err) @@ -330,8 +330,8 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { is.Equal(common.StatusDeployed, updatedRes.Info.Status) is.Equal(0, len(updatedRes.Chart.Dependencies()), "expected 0 dependencies") - expectedValues := map[string]interface{}{ - "subchart": map[string]interface{}{ + expectedValues := map[string]any{ + "subchart": map[string]any{ "enabled": false, }, } @@ -345,20 +345,20 @@ func TestUpgradeRelease_ResetThenReuseValues(t *testing.T) { t.Run("reset then reuse values should work with values", func(t *testing.T) { upAction := upgradeAction(t) - existingValues := map[string]interface{}{ + existingValues := map[string]any{ "name": "value", "maxHeapSize": "128m", "replicas": 2, } - newValues := map[string]interface{}{ + newValues := map[string]any{ "name": "newValue", "maxHeapSize": "512m", "cpu": "12m", } - newChartValues := map[string]interface{}{ + newChartValues := map[string]any{ "memory": "256m", } - expectedValues := map[string]interface{}{ + expectedValues := map[string]any{ "name": "newValue", "maxHeapSize": "512m", "cpu": "12m", @@ -411,7 +411,7 @@ func TestUpgradeRelease_Pending(t *testing.T) { rel2.Version = 2 require.NoError(t, upAction.cfg.Releases.Create(rel2)) - vals := map[string]interface{}{} + vals := map[string]any{} _, err := upAction.Run(rel.Name, buildChart(), vals) req.Contains(err.Error(), "progress", err) @@ -431,7 +431,7 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { failer.WaitDuration = 10 * time.Second upAction.cfg.KubeClient = failer upAction.WaitStrategy = kube.StatusWatcherStrategy - vals := map[string]interface{}{} + vals := map[string]any{} ctx, cancel := context.WithCancel(t.Context()) time.AfterFunc(time.Second, cancel) @@ -460,7 +460,7 @@ func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) { failer.WaitDuration = 5 * time.Second upAction.cfg.KubeClient = failer upAction.RollbackOnFailure = true - vals := map[string]interface{}{} + vals := map[string]any{} ctx, cancel := context.WithCancel(t.Context()) time.AfterFunc(time.Second, cancel) @@ -590,7 +590,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) { req.NoError(upAction.cfg.Releases.Create(rel)) upAction.DryRunStrategy = DryRunClient - vals := map[string]interface{}{} + vals := map[string]any{} ctx, done := context.WithCancel(t.Context()) resi, err := upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) @@ -610,7 +610,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) { // Test the case for hiding the secret to ensure it is not displayed upAction.HideSecret = true - vals = map[string]interface{}{} + vals = map[string]any{} ctx, done = context.WithCancel(t.Context()) resi, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) @@ -630,7 +630,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) { // Ensure in a dry run mode when using HideSecret upAction.DryRunStrategy = DryRunNone - vals = map[string]interface{}{} + vals = map[string]any{} ctx, done = context.WithCancel(t.Context()) _, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) @@ -752,7 +752,7 @@ func TestUpgradeRun_UnreachableKubeClient(t *testing.T) { config.KubeClient = &failingKubeClient client := NewUpgrade(config) - vals := map[string]interface{}{} + vals := map[string]any{} result, err := client.Run("", buildChart(), vals) assert.Nil(t, result) @@ -795,7 +795,7 @@ func TestUpgradeRelease_WaitOptionsPassedDownstream(t *testing.T) { // Access the underlying FailingKubeClient to check recorded options failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - vals := map[string]interface{}{} + vals := map[string]any{} _, err := upAction.Run(rel.Name, buildChart(), vals) req.NoError(err) diff --git a/pkg/chart/common/util/jsonschema.go b/pkg/chart/common/util/jsonschema.go index 873c08fdd..63ca0c274 100644 --- a/pkg/chart/common/util/jsonschema.go +++ b/pkg/chart/common/util/jsonschema.go @@ -73,7 +73,7 @@ func newHTTPURLLoader() *HTTPURLLoader { } // ValidateAgainstSchema checks that values does not violate the structure laid out in schema -func ValidateAgainstSchema(ch chart.Charter, values map[string]interface{}) error { +func ValidateAgainstSchema(ch chart.Charter, values map[string]any) error { chrt, err := chart.NewAccessor(ch) if err != nil { return err diff --git a/pkg/chart/common/values.go b/pkg/chart/common/values.go index 94958a779..17a067790 100644 --- a/pkg/chart/common/values.go +++ b/pkg/chart/common/values.go @@ -29,7 +29,7 @@ import ( const GlobalKey = "global" // Values represents a collection of chart values. -type Values map[string]interface{} +type Values map[string]any // YAML encodes the Values into a YAML string. func (v Values) YAML() (string, error) { @@ -64,9 +64,9 @@ func (v Values) Table(name string) (Values, error) { // AsMap is a utility function for converting Values to a map[string]interface{}. // // It protects against nil map panics. -func (v Values) AsMap() map[string]interface{} { +func (v Values) AsMap() map[string]any { if len(v) == 0 { - return map[string]interface{}{} + return map[string]any{} } return v } @@ -86,7 +86,7 @@ func tableLookup(v Values, simple string) (Values, error) { if !ok { return v, ErrNoTable{simple} } - if vv, ok := v2.(map[string]interface{}); ok { + if vv, ok := v2.(map[string]any); ok { return vv, nil } @@ -113,7 +113,7 @@ func ReadValues(data []byte) (vals Values, err error) { func ReadValuesFile(filename string) (Values, error) { data, err := os.ReadFile(filename) if err != nil { - return map[string]interface{}{}, err + return map[string]any{}, err } return ReadValues(data) } @@ -129,8 +129,8 @@ type ReleaseOptions struct { } // istable is a special-purpose function to see if the present thing matches the definition of a YAML table. -func istable(v interface{}) bool { - _, ok := v.(map[string]interface{}) +func istable(v any) bool { + _, ok := v.(map[string]any) return ok } @@ -141,14 +141,14 @@ func istable(v interface{}) bool { // chapter: // one: // title: "Loomings" -func (v Values) PathValue(path string) (interface{}, error) { +func (v Values) PathValue(path string) (any, error) { if path == "" { return nil, errors.New("YAML path cannot be empty") } return v.pathValue(parsePath(path)) } -func (v Values) pathValue(path []string) (interface{}, error) { +func (v Values) pathValue(path []string) (any, error) { if len(path) == 1 { // if exists must be root key not table if _, ok := v[path[0]]; ok && !istable(v[path[0]]) { diff --git a/pkg/chart/common/values_test.go b/pkg/chart/common/values_test.go index 3cceeb2b5..940d0d451 100644 --- a/pkg/chart/common/values_test.go +++ b/pkg/chart/common/values_test.go @@ -135,7 +135,7 @@ chapter: } } -func matchValues(t *testing.T, data map[string]interface{}) { +func matchValues(t *testing.T, data map[string]any) { t.Helper() if data["poet"] != "Coleridge" { t.Errorf("Unexpected poet: %s", data["poet"]) @@ -160,7 +160,7 @@ func matchValues(t *testing.T, data map[string]interface{}) { } } -func ttpl(tpl string, v map[string]interface{}) (string, error) { +func ttpl(tpl string, v map[string]any) (string, error) { var b bytes.Buffer tt := template.Must(template.New("t").Parse(tpl)) err := tt.Execute(&b, v) diff --git a/pkg/chart/v2/chart.go b/pkg/chart/v2/chart.go index d77a53ddc..4cfc2b890 100644 --- a/pkg/chart/v2/chart.go +++ b/pkg/chart/v2/chart.go @@ -48,11 +48,11 @@ type Chart struct { // Templates for this chart. Templates []*common.File `json:"templates"` // Values are default config for this chart. - Values map[string]interface{} `json:"values"` + Values map[string]any `json:"values"` // Schema is an optional JSON schema for imposing structure on Values Schema []byte `json:"schema"` // SchemaModTime the schema was last modified - SchemaModTime time.Time `json:"schemamodtime,omitempty"` + SchemaModTime time.Time `json:"schemamodtime"` // Files are miscellaneous files in a chart archive, // e.g. README, LICENSE, etc. Files []*common.File `json:"files"` diff --git a/pkg/chart/v2/dependency.go b/pkg/chart/v2/dependency.go index 8a590a036..5a92ef305 100644 --- a/pkg/chart/v2/dependency.go +++ b/pkg/chart/v2/dependency.go @@ -44,7 +44,7 @@ type Dependency struct { Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` // ImportValues holds the mapping of source values to parent key to be imported. Each item can be a // string or pair of child/parent sublist items. - ImportValues []interface{} `json:"import-values,omitempty" yaml:"import-values,omitempty"` + ImportValues []any `json:"import-values,omitempty" yaml:"import-values,omitempty"` // Alias usable alias to be used for the chart Alias string `json:"alias,omitempty" yaml:"alias,omitempty"` } diff --git a/pkg/chart/v2/errors.go b/pkg/chart/v2/errors.go index eeef75315..5f7f5e738 100644 --- a/pkg/chart/v2/errors.go +++ b/pkg/chart/v2/errors.go @@ -25,6 +25,6 @@ func (v ValidationError) Error() string { } // ValidationErrorf takes a message and formatting options and creates a ValidationError -func ValidationErrorf(msg string, args ...interface{}) ValidationError { +func ValidationErrorf(msg string, args ...any) ValidationError { return ValidationError(fmt.Sprintf(msg, args...)) } diff --git a/pkg/chart/v2/lint/lint.go b/pkg/chart/v2/lint/lint.go index 1c871d936..7f6f26320 100644 --- a/pkg/chart/v2/lint/lint.go +++ b/pkg/chart/v2/lint/lint.go @@ -43,7 +43,7 @@ func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption { } } -func RunAll(baseDir string, values map[string]interface{}, namespace string, options ...LinterOption) support.Linter { +func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter { chartDir, _ := filepath.Abs(baseDir) diff --git a/pkg/chart/v2/lint/rules/template_test.go b/pkg/chart/v2/lint/rules/template_test.go index c08ba6cc3..b6a62e8fd 100644 --- a/pkg/chart/v2/lint/rules/template_test.go +++ b/pkg/chart/v2/lint/rules/template_test.go @@ -49,7 +49,7 @@ func TestValidateAllowedExtension(t *testing.T) { } } -var values = map[string]interface{}{"nameOverride": "", "httpPort": 80} +var values = map[string]any{"nameOverride": "", "httpPort": 80} const namespace = "testNamespace" @@ -264,7 +264,7 @@ func TestStrictTemplateParsingMapError(t *testing.T) { APIVersion: "v2", Version: "0.1.0", }, - Values: map[string]interface{}{ + Values: map[string]any{ "mymap": map[string]string{ "key1": "val1", }, diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index 397745dd6..dad988605 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -508,7 +508,7 @@ func TestLoadInvalidArchive(t *testing.T) { func TestLoadValues(t *testing.T) { testCases := map[string]struct { data []byte - expctedValues map[string]interface{} + expctedValues map[string]any }{ "It should load values correctly": { data: []byte(` @@ -517,11 +517,11 @@ foo: bar: version: v2 `), - expctedValues: map[string]interface{}{ - "foo": map[string]interface{}{ + expctedValues: map[string]any{ + "foo": map[string]any{ "image": "foo:v1", }, - "bar": map[string]interface{}{ + "bar": map[string]any{ "version": "v2", }, }, @@ -536,11 +536,11 @@ bar: foo: image: foo:v2 `), - expctedValues: map[string]interface{}{ - "foo": map[string]interface{}{ + expctedValues: map[string]any{ + "foo": map[string]any{ "image": "foo:v2", }, - "bar": map[string]interface{}{ + "bar": map[string]any{ "version": "v2", }, }, @@ -560,24 +560,24 @@ foo: } func TestMergeValuesV2(t *testing.T) { - nestedMap := map[string]interface{}{ + nestedMap := map[string]any{ "foo": "bar", "baz": map[string]string{ "cool": "stuff", }, } - anotherNestedMap := map[string]interface{}{ + anotherNestedMap := map[string]any{ "foo": "bar", "baz": map[string]string{ "cool": "things", "awesome": "stuff", }, } - flatMap := map[string]interface{}{ + flatMap := map[string]any{ "foo": "bar", "baz": "stuff", } - anotherFlatMap := map[string]interface{}{ + anotherFlatMap := map[string]any{ "testing": "fun", } @@ -600,7 +600,7 @@ func TestMergeValuesV2(t *testing.T) { } testMap = MergeMaps(anotherFlatMap, anotherNestedMap) - expectedMap := map[string]interface{}{ + expectedMap := map[string]any{ "testing": "fun", "foo": "bar", "baz": map[string]string{ diff --git a/pkg/chart/v2/util/dependencies.go b/pkg/chart/v2/util/dependencies.go index aa242e0ca..abd673f9d 100644 --- a/pkg/chart/v2/util/dependencies.go +++ b/pkg/chart/v2/util/dependencies.go @@ -142,7 +142,7 @@ func copyMetadata(metadata *chart.Metadata) *chart.Metadata { } // processDependencyEnabled removes disabled charts from dependencies -func processDependencyEnabled(c *chart.Chart, v map[string]interface{}, path string) error { +func processDependencyEnabled(c *chart.Chart, v map[string]any, path string) error { if c.Metadata.Dependencies == nil { return nil } @@ -228,7 +228,7 @@ Loop: } // pathToMap creates a nested map given a YAML path in dot notation. -func pathToMap(path string, data map[string]interface{}) map[string]interface{} { +func pathToMap(path string, data map[string]any) map[string]any { if path == "." { return data } @@ -237,13 +237,13 @@ func pathToMap(path string, data map[string]interface{}) map[string]interface{} func parsePath(key string) []string { return strings.Split(key, ".") } -func set(path []string, data map[string]interface{}) map[string]interface{} { +func set(path []string, data map[string]any) map[string]any { if len(path) == 0 { return nil } cur := data for i := len(path) - 1; i >= 0; i-- { - cur = map[string]interface{}{path[i]: cur} + cur = map[string]any{path[i]: cur} } return cur } @@ -264,13 +264,13 @@ func processImportValues(c *chart.Chart, merge bool) error { if err != nil { return err } - b := make(map[string]interface{}) + b := make(map[string]any) // import values from each dependency if specified in import-values for _, r := range c.Metadata.Dependencies { - var outiv []interface{} + var outiv []any for _, riv := range r.ImportValues { switch iv := riv.(type) { - case map[string]interface{}: + case map[string]any: child := fmt.Sprintf("%v", iv["child"]) parent := fmt.Sprintf("%v", iv["parent"]) @@ -337,27 +337,27 @@ func processImportValues(c *chart.Chart, merge bool) error { return nil } -func deepCopyMap(vals map[string]interface{}) map[string]interface{} { +func deepCopyMap(vals map[string]any) map[string]any { valsCopy, err := copystructure.Copy(vals) if err != nil { return vals } - return valsCopy.(map[string]interface{}) + return valsCopy.(map[string]any) } -func trimNilValues(vals map[string]interface{}) map[string]interface{} { +func trimNilValues(vals map[string]any) map[string]any { valsCopy, err := copystructure.Copy(vals) if err != nil { return vals } - valsCopyMap := valsCopy.(map[string]interface{}) + valsCopyMap := valsCopy.(map[string]any) for key, val := range valsCopyMap { if val == nil { // Iterate over the values and remove nil keys delete(valsCopyMap, key) } else if istable(val) { // Recursively call into ourselves to remove keys from inner tables - valsCopyMap[key] = trimNilValues(val.(map[string]interface{})) + valsCopyMap[key] = trimNilValues(val.(map[string]any)) } } @@ -365,8 +365,8 @@ func trimNilValues(vals map[string]interface{}) map[string]interface{} { } // istable is a special-purpose function to see if the present thing matches the definition of a YAML table. -func istable(v interface{}) bool { - _, ok := v.(map[string]interface{}) +func istable(v any) bool { + _, ok := v.(map[string]any) return ok } diff --git a/pkg/chart/v2/util/dependencies_test.go b/pkg/chart/v2/util/dependencies_test.go index c817b0b89..d9619e76f 100644 --- a/pkg/chart/v2/util/dependencies_test.go +++ b/pkg/chart/v2/util/dependencies_test.go @@ -63,7 +63,7 @@ func TestLoadDependency(t *testing.T) { } func TestDependencyEnabled(t *testing.T) { - type M = map[string]interface{} + type M = map[string]any tests := []struct { name string v M diff --git a/pkg/chart/v2/util/save_test.go b/pkg/chart/v2/util/save_test.go index 6d4e2c8cd..f8e137d0c 100644 --- a/pkg/chart/v2/util/save_test.go +++ b/pkg/chart/v2/util/save_test.go @@ -157,7 +157,7 @@ func TestSavePreservesTimestamps(t *testing.T) { Version: "1.2.3", }, ModTime: initialCreateTime, - Values: map[string]interface{}{ + Values: map[string]any{ "imageName": "testimage", "imageId": 42, }, diff --git a/pkg/cli/output/output.go b/pkg/cli/output/output.go index 28d503741..6364c90c3 100644 --- a/pkg/cli/output/output.go +++ b/pkg/cli/output/output.go @@ -102,7 +102,7 @@ type Writer interface { // EncodeJSON is a helper function to decorate any error message with a bit more // context and avoid writing the same code over and over for printers. -func EncodeJSON(out io.Writer, obj interface{}) error { +func EncodeJSON(out io.Writer, obj any) error { enc := json.NewEncoder(out) err := enc.Encode(obj) if err != nil { @@ -113,7 +113,7 @@ func EncodeJSON(out io.Writer, obj interface{}) error { // EncodeYAML is a helper function to decorate any error message with a bit more // context and avoid writing the same code over and over for printers -func EncodeYAML(out io.Writer, obj interface{}) error { +func EncodeYAML(out io.Writer, obj any) error { raw, err := yaml.Marshal(obj) if err != nil { return fmt.Errorf("unable to write YAML output: %w", err) diff --git a/pkg/cli/values/options_test.go b/pkg/cli/values/options_test.go index fe1afc5d2..b8b1499a0 100644 --- a/pkg/cli/values/options_test.go +++ b/pkg/cli/values/options_test.go @@ -298,7 +298,7 @@ func TestMergeValuesCLI(t *testing.T) { tests := []struct { name string opts Options - expected map[string]interface{} + expected map[string]any wantErr bool }{ { @@ -306,8 +306,8 @@ func TestMergeValuesCLI(t *testing.T) { opts: Options{ JSONValues: []string{`{"foo": {"bar": "baz"}}`}, }, - expected: map[string]interface{}{ - "foo": map[string]interface{}{ + expected: map[string]any{ + "foo": map[string]any{ "bar": "baz", }, }, @@ -317,9 +317,9 @@ func TestMergeValuesCLI(t *testing.T) { opts: Options{ JSONValues: []string{"foo.bar=[1,2,3]"}, }, - expected: map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": []interface{}{1.0, 2.0, 3.0}, + expected: map[string]any{ + "foo": map[string]any{ + "bar": []any{1.0, 2.0, 3.0}, }, }, }, @@ -328,7 +328,7 @@ func TestMergeValuesCLI(t *testing.T) { opts: Options{ Values: []string{"foo=bar"}, }, - expected: map[string]interface{}{ + expected: map[string]any{ "foo": "bar", }, }, @@ -337,7 +337,7 @@ func TestMergeValuesCLI(t *testing.T) { opts: Options{ StringValues: []string{"foo=123"}, }, - expected: map[string]interface{}{ + expected: map[string]any{ "foo": "123", }, }, @@ -346,7 +346,7 @@ func TestMergeValuesCLI(t *testing.T) { opts: Options{ LiteralValues: []string{"foo=true"}, }, - expected: map[string]interface{}{ + expected: map[string]any{ "foo": "true", }, }, @@ -358,7 +358,7 @@ func TestMergeValuesCLI(t *testing.T) { JSONValues: []string{`{"c": "foo1"}`}, LiteralValues: []string{"d=bar1"}, }, - expected: map[string]interface{}{ + expected: map[string]any{ "a": "foo", "b": "bar", "c": "foo1", diff --git a/pkg/cmd/create_test.go b/pkg/cmd/create_test.go index 57e2aaf5f..6e95094c7 100644 --- a/pkg/cmd/create_test.go +++ b/pkg/cmd/create_test.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "testing" chartv3 "helm.sh/helm/v4/internal/chart/v3" @@ -188,13 +189,7 @@ func TestCreateStarterCmd(t *testing.T) { } // Verify custom template exists - found := false - for _, name := range templates { - if name == "templates/foo.tpl" { - found = true - break - } - } + found := slices.Contains(templates, "templates/foo.tpl") if !found { t.Error("Did not find foo.tpl") } diff --git a/pkg/cmd/get_all.go b/pkg/cmd/get_all.go index 32744796c..bce89d7d3 100644 --- a/pkg/cmd/get_all.go +++ b/pkg/cmd/get_all.go @@ -53,7 +53,7 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return err } if template != "" { - data := map[string]interface{}{ + data := map[string]any{ "Release": res, } return tpl(template, data, out) diff --git a/pkg/cmd/get_values.go b/pkg/cmd/get_values.go index 02b195551..c6a89b00c 100644 --- a/pkg/cmd/get_values.go +++ b/pkg/cmd/get_values.go @@ -33,7 +33,7 @@ This command downloads a values file for a given release. ` type valuesWriter struct { - vals map[string]interface{} + vals map[string]any allValues bool } diff --git a/pkg/cmd/history.go b/pkg/cmd/history.go index b294a9da7..11cb7afb5 100644 --- a/pkg/cmd/history.go +++ b/pkg/cmd/history.go @@ -106,7 +106,7 @@ type releaseInfoJSON struct { // It handles empty string time fields by treating them as zero values. func (r *releaseInfo) UnmarshalJSON(data []byte) error { // First try to unmarshal into a map to handle empty string time fields - var raw map[string]interface{} + var raw map[string]any if err := json.Unmarshal(data, &raw); err != nil { return err } diff --git a/pkg/cmd/history_test.go b/pkg/cmd/history_test.go index d8adc2d19..b536bca36 100644 --- a/pkg/cmd/history_test.go +++ b/pkg/cmd/history_test.go @@ -321,7 +321,7 @@ func TestReleaseInfoEmptyStringRoundTrip(t *testing.T) { data, err := json.Marshal(&info) require.NoError(t, err) - var result map[string]interface{} + var result map[string]any err = json.Unmarshal(data, &result) require.NoError(t, err) diff --git a/pkg/cmd/printer.go b/pkg/cmd/printer.go index 30238f5bb..3a3840cfc 100644 --- a/pkg/cmd/printer.go +++ b/pkg/cmd/printer.go @@ -21,7 +21,7 @@ import ( "text/template" ) -func tpl(t string, vals map[string]interface{}, out io.Writer) error { +func tpl(t string, vals map[string]any, out io.Writer) error { tt, err := template.New("_").Parse(t) if err != nil { return err diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index f5db7e158..e39e63845 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -129,8 +129,8 @@ func warnWrap(warn string) string { // 'include' needs to be defined in the scope of a 'tpl' template as // well as regular file-loaded templates. -func includeFun(t *template.Template, includedNames map[string]int) func(string, interface{}) (string, error) { - return func(name string, data interface{}) (string, error) { +func includeFun(t *template.Template, includedNames map[string]int) func(string, any) (string, error) { + return func(name string, data any) (string, error) { var buf strings.Builder if v, ok := includedNames[name]; ok { if v > recursionMaxNums { @@ -150,8 +150,8 @@ func includeFun(t *template.Template, includedNames map[string]int) func(string, // As does 'tpl', so that nested calls to 'tpl' see the templates // defined by their enclosing contexts. -func tplFun(parent *template.Template, includedNames map[string]int, strict bool) func(string, interface{}) (string, error) { - return func(tpl string, vals interface{}) (string, error) { +func tplFun(parent *template.Template, includedNames map[string]int, strict bool) func(string, any) (string, error) { + return func(tpl string, vals any) (string, error) { t, err := parent.Clone() if err != nil { return "", fmt.Errorf("cannot clone template: %w", err) @@ -204,7 +204,7 @@ func (e Engine) initFunMap(t *template.Template) { funcMap["tpl"] = tplFun(t, includedNames, e.Strict) // Add the `required` function here so we can use lintMode - funcMap["required"] = func(warn string, val interface{}) (interface{}, error) { + funcMap["required"] = func(warn string, val any) (any, error) { if val == nil { if e.LintMode { // Don't fail on missing required values when linting @@ -410,9 +410,9 @@ func parseTemplateSimpleErrorString(remainder string) (TraceableError, bool) { // Executing form: ": executing \"\" at <>: [ template:...]" // Matches https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=141 func parseTemplateExecutingAtErrorType(remainder string) (TraceableError, bool) { - if idx := strings.Index(remainder, ": executing "); idx != -1 { - templateName := remainder[:idx] - after := remainder[idx+len(": executing "):] + if before, after, ok := strings.Cut(remainder, ": executing "); ok { + templateName := before + after := after if len(after) == 0 || after[0] != '"' { return TraceableError{}, false } @@ -431,12 +431,12 @@ func parseTemplateExecutingAtErrorType(remainder string) (TraceableError, bool) return TraceableError{}, false } afterAt := afterFunc[len(atPrefix):] - endLoc := strings.Index(afterAt, ">: ") - if endLoc == -1 { + before, after0, ok := strings.Cut(afterAt, ">: ") + if !ok { return TraceableError{}, false } - locationName := afterAt[:endLoc] - errMsg := afterAt[endLoc+len(">: "):] + locationName := before + errMsg := after0 // trim chained next error starting with space + "template:" if present if cut := strings.Index(errMsg, " template:"); cut != -1 { @@ -535,9 +535,9 @@ func allTemplates(c ci.Charter, vals common.Values) map[string]renderable { // // As it recurses, it also sets the values to be appropriate for the template // scope. -func recAllTpls(c ci.Charter, templates map[string]renderable, values common.Values) map[string]interface{} { +func recAllTpls(c ci.Charter, templates map[string]renderable, values common.Values) map[string]any { vals := values.AsMap() - subCharts := make(map[string]interface{}) + subCharts := make(map[string]any) accessor, err := ci.NewAccessor(c) if err != nil { slog.Error("error accessing chart", "error", err) @@ -545,7 +545,7 @@ func recAllTpls(c ci.Charter, templates map[string]renderable, values common.Val chartMetaData := accessor.MetadataAsMap() chartMetaData["IsRoot"] = accessor.IsRoot() - next := map[string]interface{}{ + next := map[string]any{ "Chart": chartMetaData, "Files": newFiles(accessor.Files()), "Release": vals["Release"], diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index c9cdf79c3..c80c65c65 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -104,14 +104,14 @@ func TestRender(t *testing.T) { {Name: "templates/test4", ModTime: modTime, Data: []byte("{{toJson .Values}}")}, {Name: "templates/test5", ModTime: modTime, Data: []byte("{{getHostByName \"helm.sh\"}}")}, }, - Values: map[string]interface{}{"outer": "DEFAULT", "inner": "DEFAULT"}, + Values: map[string]any{"outer": "DEFAULT", "inner": "DEFAULT"}, } - vals := map[string]interface{}{ - "Values": map[string]interface{}{ + vals := map[string]any{ + "Values": map[string]any{ "outer": "spouter", "inner": "inn", - "global": map[string]interface{}{ + "global": map[string]any{ "callme": "Ishmael", }, }, @@ -226,11 +226,11 @@ func TestRenderWithDNS(t *testing.T) { Templates: []*common.File{ {Name: "templates/test1", ModTime: time.Now(), Data: []byte("{{getHostByName \"helm.sh\"}}")}, }, - Values: map[string]interface{}{}, + Values: map[string]any{}, } - vals := map[string]interface{}{ - "Values": map[string]interface{}{}, + vals := map[string]any{ + "Values": map[string]any{}, } v, err := util.CoalesceValues(c, vals) @@ -277,15 +277,15 @@ var _ ClientProvider = &testClientProvider{} // makeUnstructured is a convenience function for single-line creation of Unstructured objects. func makeUnstructured(apiVersion, kind, name, namespace string) *unstructured.Unstructured { - ret := &unstructured.Unstructured{Object: map[string]interface{}{ + ret := &unstructured.Unstructured{Object: map[string]any{ "apiVersion": apiVersion, "kind": kind, - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": name, }, }} if namespace != "" { - ret.Object["metadata"].(map[string]interface{})["namespace"] = namespace + ret.Object["metadata"].(map[string]any)["namespace"] = namespace } return ret } @@ -356,7 +356,7 @@ func TestRenderWithClientProvider(t *testing.T) { Name: "moby", Version: "1.2.3", }, - Values: map[string]interface{}{}, + Values: map[string]any{}, } modTime := time.Now() @@ -368,8 +368,8 @@ func TestRenderWithClientProvider(t *testing.T) { }) } - vals := map[string]interface{}{ - "Values": map[string]interface{}{}, + vals := map[string]any{ + "Values": map[string]any{}, } v, err := util.CoalesceValues(c, vals) @@ -401,11 +401,11 @@ func TestRenderWithClientProvider_error(t *testing.T) { Templates: []*common.File{ {Name: "templates/error", ModTime: time.Now(), Data: []byte(`{{ lookup "v1" "Error" "" "" }}`)}, }, - Values: map[string]interface{}{}, + Values: map[string]any{}, } - vals := map[string]interface{}{ - "Values": map[string]interface{}{}, + vals := map[string]any{ + "Values": map[string]any{}, } v, err := util.CoalesceValues(c, vals) @@ -438,7 +438,7 @@ func TestParallelRenderInternals(t *testing.T) { tpls := map[string]renderable{ "t": { tpl: `{{.val}}`, - vals: map[string]interface{}{"val": tt}, + vals: map[string]any{"val": tt}, }, } out, err := e.render(tpls) @@ -455,7 +455,7 @@ func TestParallelRenderInternals(t *testing.T) { } func TestParseErrors(t *testing.T) { - vals := common.Values{"Values": map[string]interface{}{}} + vals := common.Values{"Values": map[string]any{}} tplsUndefinedFunction := map[string]renderable{ "undefined_function": {tpl: `{{foo}}`, vals: vals}, @@ -471,7 +471,7 @@ func TestParseErrors(t *testing.T) { } func TestExecErrors(t *testing.T) { - vals := common.Values{"Values": map[string]interface{}{}} + vals := common.Values{"Values": map[string]any{}} cases := []struct { name string tpls map[string]renderable @@ -535,7 +535,7 @@ linebreak`, } func TestFailErrors(t *testing.T) { - vals := common.Values{"Values": map[string]interface{}{}} + vals := common.Values{"Values": map[string]any{}} failtpl := `All your base are belong to us{{ fail "This is an error" }}` tplsFailed := map[string]renderable{ @@ -643,7 +643,7 @@ func TestRenderDependency(t *testing.T) { }, }) - out, err := Render(ch, map[string]interface{}{}) + out, err := Render(ch, map[string]any{}) if err != nil { t.Fatalf("failed to render chart: %s", err) } @@ -675,7 +675,7 @@ func TestRenderNestedValues(t *testing.T) { {Name: deepestpath, ModTime: modTime, Data: []byte(`And this same {{.Values.what}} that smiles {{.Values.global.when}}`)}, {Name: checkrelease, ModTime: modTime, Data: []byte(`Tomorrow will be {{default "happy" .Release.Name }}`)}, }, - Values: map[string]interface{}{"what": "milkshake", "where": "here"}, + Values: map[string]any{"what": "milkshake", "where": "here"}, } inner := &chart.Chart{ @@ -683,7 +683,7 @@ func TestRenderNestedValues(t *testing.T) { Templates: []*common.File{ {Name: innerpath, ModTime: modTime, Data: []byte(`Old {{.Values.who}} is still a-flyin'`)}, }, - Values: map[string]interface{}{"who": "Robert", "what": "glasses"}, + Values: map[string]any{"who": "Robert", "what": "glasses"}, } inner.AddDependency(deepest) @@ -693,10 +693,10 @@ func TestRenderNestedValues(t *testing.T) { {Name: outerpath, ModTime: modTime, Data: []byte(`Gather ye {{.Values.what}} while ye may`)}, {Name: subchartspath, ModTime: modTime, Data: []byte(`The glorious Lamp of {{.Subcharts.herrick.Subcharts.deepest.Values.where}}, the {{.Subcharts.herrick.Values.what}}`)}, }, - Values: map[string]interface{}{ + Values: map[string]any{ "what": "stinkweed", "who": "me", - "herrick": map[string]interface{}{ + "herrick": map[string]any{ "who": "time", "what": "Sun", }, @@ -704,15 +704,15 @@ func TestRenderNestedValues(t *testing.T) { } outer.AddDependency(inner) - injValues := map[string]interface{}{ + injValues := map[string]any{ "what": "rosebuds", - "herrick": map[string]interface{}{ - "deepest": map[string]interface{}{ + "herrick": map[string]any{ + "deepest": map[string]any{ "what": "flower", "where": "Heaven", }, }, - "global": map[string]interface{}{ + "global": map[string]any{ "when": "to-day", }, } @@ -1349,7 +1349,7 @@ NestedHelperFunctions/charts/common/templates/_helpers_2.tpl:1:49 v := common.Values{} val, _ := util.CoalesceValues(c, v) - vals := map[string]interface{}{ + vals := map[string]any{ "Values": val.AsMap(), } _, err := Render(c, vals) @@ -1383,7 +1383,7 @@ template: no template "nested_helper.name" associated with template "gotpl"` v := common.Values{} val, _ := util.CoalesceValues(c, v) - vals := map[string]interface{}{ + vals := map[string]any{ "Values": val.AsMap(), } _, err := Render(c, vals) diff --git a/pkg/engine/funcs.go b/pkg/engine/funcs.go index a97f8f104..ba842a51a 100644 --- a/pkg/engine/funcs.go +++ b/pkg/engine/funcs.go @@ -64,13 +64,13 @@ func funcMap() template.FuncMap { // This is a placeholder for the "include" function, which is // late-bound to a template. By declaring it here, we preserve the // integrity of the linter. - "include": func(string, interface{}) string { return "not implemented" }, - "tpl": func(string, interface{}) interface{} { return "not implemented" }, - "required": func(string, interface{}) (interface{}, error) { return "not implemented", nil }, + "include": func(string, any) string { return "not implemented" }, + "tpl": func(string, any) any { return "not implemented" }, + "required": func(string, any) (any, error) { return "not implemented", nil }, // Provide a placeholder for the "lookup" function, which requires a kubernetes // connection. - "lookup": func(string, string, string, string) (map[string]interface{}, error) { - return map[string]interface{}{}, nil + "lookup": func(string, string, string, string) (map[string]any, error) { + return map[string]any{}, nil }, } @@ -83,7 +83,7 @@ func funcMap() template.FuncMap { // always return a string, even on marshal error (empty string). // // This is designed to be called from a template. -func toYAML(v interface{}) string { +func toYAML(v any) string { data, err := yaml.Marshal(v) if err != nil { // Swallow errors inside of a template. @@ -97,7 +97,7 @@ func toYAML(v interface{}) string { // // This is designed to be called from a template when need to ensure that the // output YAML is valid. -func mustToYAML(v interface{}) string { +func mustToYAML(v any) string { data, err := yaml.Marshal(v) if err != nil { panic(err) @@ -105,7 +105,7 @@ func mustToYAML(v interface{}) string { return strings.TrimSuffix(string(data), "\n") } -func toYAMLPretty(v interface{}) string { +func toYAMLPretty(v any) string { var data bytes.Buffer encoder := goYaml.NewEncoder(&data) encoder.SetIndent(2) @@ -124,8 +124,8 @@ func toYAMLPretty(v interface{}) string { // YAML documents. Additionally, because its intended use is within templates // it tolerates errors. It will insert the returned error message string into // m["Error"] in the returned map. -func fromYAML(str string) map[string]interface{} { - m := map[string]interface{}{} +func fromYAML(str string) map[string]any { + m := map[string]any{} if err := yaml.Unmarshal([]byte(str), &m); err != nil { m["Error"] = err.Error() @@ -139,11 +139,11 @@ func fromYAML(str string) map[string]interface{} { // YAML documents. Additionally, because its intended use is within templates // it tolerates errors. It will insert the returned error message string as // the first and only item in the returned array. -func fromYAMLArray(str string) []interface{} { - a := []interface{}{} +func fromYAMLArray(str string) []any { + a := []any{} if err := yaml.Unmarshal([]byte(str), &a); err != nil { - a = []interface{}{err.Error()} + a = []any{err.Error()} } return a } @@ -152,7 +152,7 @@ func fromYAMLArray(str string) []interface{} { // always return a string, even on marshal error (empty string). // // This is designed to be called from a template. -func toTOML(v interface{}) string { +func toTOML(v any) string { b := bytes.NewBuffer(nil) e := toml.NewEncoder(b) err := e.Encode(v) @@ -168,8 +168,8 @@ func toTOML(v interface{}) string { // TOML documents. Additionally, because its intended use is within templates // it tolerates errors. It will insert the returned error message string into // m["Error"] in the returned map. -func fromTOML(str string) map[string]interface{} { - m := make(map[string]interface{}) +func fromTOML(str string) map[string]any { + m := make(map[string]any) if err := toml.Unmarshal([]byte(str), &m); err != nil { m["Error"] = err.Error() @@ -181,7 +181,7 @@ func fromTOML(str string) map[string]interface{} { // always return a string, even on marshal error (empty string). // // This is designed to be called from a template. -func toJSON(v interface{}) string { +func toJSON(v any) string { data, err := json.Marshal(v) if err != nil { // Swallow errors inside of a template. @@ -195,7 +195,7 @@ func toJSON(v interface{}) string { // // This is designed to be called from a template when need to ensure that the // output JSON is valid. -func mustToJSON(v interface{}) string { +func mustToJSON(v any) string { data, err := json.Marshal(v) if err != nil { panic(err) @@ -209,8 +209,8 @@ func mustToJSON(v interface{}) string { // JSON documents. Additionally, because its intended use is within templates // it tolerates errors. It will insert the returned error message string into // m["Error"] in the returned map. -func fromJSON(str string) map[string]interface{} { - m := make(map[string]interface{}) +func fromJSON(str string) map[string]any { + m := make(map[string]any) if err := json.Unmarshal([]byte(str), &m); err != nil { m["Error"] = err.Error() @@ -224,11 +224,11 @@ func fromJSON(str string) map[string]interface{} { // JSON documents. Additionally, because its intended use is within templates // it tolerates errors. It will insert the returned error message string as // the first and only item in the returned array. -func fromJSONArray(str string) []interface{} { - a := []interface{}{} +func fromJSONArray(str string) []any { + a := []any{} if err := json.Unmarshal([]byte(str), &a); err != nil { - a = []interface{}{err.Error()} + a = []any{err.Error()} } return a } diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go index 71a72e2e4..48202454e 100644 --- a/pkg/engine/funcs_test.go +++ b/pkg/engine/funcs_test.go @@ -28,19 +28,19 @@ func TestFuncs(t *testing.T) { //TODO write tests for failure cases tests := []struct { tpl, expect string - vars interface{} + vars any }{{ tpl: `{{ toYaml . }}`, expect: `foo: bar`, - vars: map[string]interface{}{"foo": "bar"}, + vars: map[string]any{"foo": "bar"}, }, { tpl: `{{ toYamlPretty . }}`, expect: "baz:\n - 1\n - 2\n - 3", - vars: map[string]interface{}{"baz": []int{1, 2, 3}}, + vars: map[string]any{"baz": []int{1, 2, 3}}, }, { tpl: `{{ toToml . }}`, expect: "foo = \"bar\"\n", - vars: map[string]interface{}{"foo": "bar"}, + vars: map[string]any{"foo": "bar"}, }, { tpl: `{{ fromToml . }}`, expect: "map[hello:world]", @@ -68,7 +68,7 @@ keyInElement1 = "valueInElement1"`, }, { tpl: `{{ toJson . }}`, expect: `{"foo":"bar"}`, - vars: map[string]interface{}{"foo": "bar"}, + vars: map[string]any{"foo": "bar"}, }, { tpl: `{{ fromYaml . }}`, expect: "map[hello:world]", @@ -109,11 +109,11 @@ keyInElement1 = "valueInElement1"`, }, { tpl: `{{ merge .dict (fromYaml .yaml) }}`, expect: `map[a:map[b:c]]`, - vars: map[string]interface{}{"dict": map[string]interface{}{"a": map[string]interface{}{"b": "c"}}, "yaml": `{"a":{"b":"d"}}`}, + vars: map[string]any{"dict": map[string]any{"a": map[string]any{"b": "c"}}, "yaml": `{"a":{"b":"d"}}`}, }, { tpl: `{{ merge (fromYaml .yaml) .dict }}`, expect: `map[a:map[b:d]]`, - vars: map[string]interface{}{"dict": map[string]interface{}{"a": map[string]interface{}{"b": "c"}}, "yaml": `{"a":{"b":"d"}}`}, + vars: map[string]any{"dict": map[string]any{"a": map[string]any{"b": "c"}}, "yaml": `{"a":{"b":"d"}}`}, }, { tpl: `{{ fromYaml . }}`, expect: `map[Error:error unmarshaling JSON: while decoding JSON: json: cannot unmarshal array into Go value of type map[string]interface {}]`, @@ -136,15 +136,15 @@ keyInElement1 = "valueInElement1"`, assert.Equal(t, tt.expect, b.String(), tt.tpl) } - loopMap := map[string]interface{}{ + loopMap := map[string]any{ "foo": "bar", } - loopMap["loop"] = []interface{}{loopMap} + loopMap["loop"] = []any{loopMap} mustFuncsTests := []struct { tpl string - expect interface{} - vars interface{} + expect any + vars any }{{ tpl: `{{ mustToYaml . }}`, vars: loopMap, @@ -186,34 +186,34 @@ keyInElement1 = "valueInElement1"`, // be used to accidentally update mergo. This test and message should catch // the problem and explain why it's happening. func TestMerge(t *testing.T) { - dict := map[string]interface{}{ - "src2": map[string]interface{}{ + dict := map[string]any{ + "src2": map[string]any{ "h": 10, "i": "i", "j": "j", }, - "src1": map[string]interface{}{ + "src1": map[string]any{ "a": 1, "b": 2, - "d": map[string]interface{}{ + "d": map[string]any{ "e": "four", }, "g": []int{6, 7}, "i": "aye", "j": "jay", - "k": map[string]interface{}{ + "k": map[string]any{ "l": false, }, }, - "dst": map[string]interface{}{ + "dst": map[string]any{ "a": "one", "c": 3, - "d": map[string]interface{}{ + "d": map[string]any{ "f": 5, }, "g": []int{8, 9}, "i": "eye", - "k": map[string]interface{}{ + "k": map[string]any{ "l": true, }, }, @@ -223,11 +223,11 @@ func TestMerge(t *testing.T) { err := template.Must(template.New("test").Funcs(funcMap()).Parse(tpl)).Execute(&b, dict) assert.NoError(t, err) - expected := map[string]interface{}{ + expected := map[string]any{ "a": "one", // key overridden "b": 2, // merged from src1 "c": 3, // merged from dst - "d": map[string]interface{}{ // deep merge + "d": map[string]any{ // deep merge "e": "four", "f": 5, }, @@ -235,7 +235,7 @@ func TestMerge(t *testing.T) { "h": 10, // merged from src2 "i": "eye", // overridden twice "j": "jay", // overridden and merged - "k": map[string]interface{}{ + "k": map[string]any{ "l": true, // overridden }, } diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index d2dd57872..0bcf766f0 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -271,7 +271,7 @@ func getRuntimeObjFromManifests(t *testing.T, manifests []string) []runtime.Obje t.Helper() objects := []runtime.Object{} for _, manifest := range manifests { - m := make(map[string]interface{}) + m := make(map[string]any) err := yaml.Unmarshal([]byte(manifest), &m) assert.NoError(t, err) resource := &unstructured.Unstructured{Object: m} diff --git a/pkg/kube/wait_test.go b/pkg/kube/wait_test.go index d96f2c486..1be1d30b9 100644 --- a/pkg/kube/wait_test.go +++ b/pkg/kube/wait_test.go @@ -39,7 +39,7 @@ import ( func TestSelectorsForObject(t *testing.T) { tests := []struct { name string - object interface{} + object any expectError bool errorContains string expectedLabels map[string]string diff --git a/pkg/provenance/sign.go b/pkg/provenance/sign.go index 57af1ad42..f878698fb 100644 --- a/pkg/provenance/sign.go +++ b/pkg/provenance/sign.go @@ -332,7 +332,7 @@ func parseMessageBlock(data []byte) (*SumCollection, error) { // // This is the generic version that can work with any metadata type. // The metadata parameter should be a pointer to a struct that can be unmarshaled from YAML. -func ParseMessageBlock(data []byte, metadata interface{}, sums *SumCollection) error { +func ParseMessageBlock(data []byte, metadata any, sums *SumCollection) error { parts := bytes.Split(data, []byte("\n...\n")) if len(parts) < 2 { return errors.New("message block must have at least two parts") diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index c82b165bc..22b74c32e 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -137,7 +137,7 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool) { config.HTTP.Addr = ln.Addr().String() config.HTTP.DrainTimeout = time.Duration(10) * time.Second - config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}} + config.Storage = map[string]configuration.Parameters{"inmemory": map[string]any{}} config.Auth = configuration.Auth{ "htpasswd": configuration.Parameters{ diff --git a/pkg/release/interfaces.go b/pkg/release/interfaces.go index aaa5a756f..c758de944 100644 --- a/pkg/release/interfaces.go +++ b/pkg/release/interfaces.go @@ -22,9 +22,9 @@ import ( "helm.sh/helm/v4/pkg/chart" ) -type Releaser interface{} +type Releaser any -type Hook interface{} +type Hook any type Accessor interface { Name() string diff --git a/pkg/release/v1/hook.go b/pkg/release/v1/hook.go index f0a370c15..5c382cd77 100644 --- a/pkg/release/v1/hook.go +++ b/pkg/release/v1/hook.go @@ -86,7 +86,7 @@ type Hook struct { // Events are the events that this hook fires on. Events []HookEvent `json:"events,omitempty"` // LastRun indicates the date/time this was last run. - LastRun HookExecution `json:"last_run,omitempty"` + LastRun HookExecution `json:"last_run"` // Weight indicates the sort order for execution among similar Hook type Weight int `json:"weight,omitempty"` // DeletePolicies are the policies that indicate when to delete the hook @@ -133,7 +133,7 @@ type hookExecutionJSON struct { // It handles empty string time fields by treating them as zero values. func (h *HookExecution) UnmarshalJSON(data []byte) error { // First try to unmarshal into a map to handle empty string time fields - var raw map[string]interface{} + var raw map[string]any if err := json.Unmarshal(data, &raw); err != nil { return err } diff --git a/pkg/release/v1/hook_test.go b/pkg/release/v1/hook_test.go index cea2568bc..f3b8811a6 100644 --- a/pkg/release/v1/hook_test.go +++ b/pkg/release/v1/hook_test.go @@ -220,7 +220,7 @@ func TestHookExecutionEmptyStringRoundTrip(t *testing.T) { data, err := json.Marshal(&exec) require.NoError(t, err) - var result map[string]interface{} + var result map[string]any err = json.Unmarshal(data, &result) require.NoError(t, err) diff --git a/pkg/release/v1/info.go b/pkg/release/v1/info.go index f895fdf6c..e6bfc1b6f 100644 --- a/pkg/release/v1/info.go +++ b/pkg/release/v1/info.go @@ -57,7 +57,7 @@ type infoJSON struct { // It handles empty string time fields by treating them as zero values. func (i *Info) UnmarshalJSON(data []byte) error { // First try to unmarshal into a map to handle empty string time fields - var raw map[string]interface{} + var raw map[string]any if err := json.Unmarshal(data, &raw); err != nil { return err } diff --git a/pkg/release/v1/info_test.go b/pkg/release/v1/info_test.go index 0fff78f76..6cff4db64 100644 --- a/pkg/release/v1/info_test.go +++ b/pkg/release/v1/info_test.go @@ -272,7 +272,7 @@ func TestInfoEmptyStringRoundTrip(t *testing.T) { data, err := json.Marshal(&info) require.NoError(t, err) - var result map[string]interface{} + var result map[string]any err = json.Unmarshal(data, &result) require.NoError(t, err) diff --git a/pkg/release/v1/mock.go b/pkg/release/v1/mock.go index dc135a24a..a26044bd7 100644 --- a/pkg/release/v1/mock.go +++ b/pkg/release/v1/mock.go @@ -123,7 +123,7 @@ func Mock(opts *MockReleaseOptions) *Release { Name: name, Info: info, Chart: ch, - Config: map[string]interface{}{"name": "value"}, + Config: map[string]any{"name": "value"}, Version: version, Namespace: namespace, Hooks: []*Hook{ diff --git a/pkg/release/v1/release.go b/pkg/release/v1/release.go index 454ee6eb7..3bbc0e4ce 100644 --- a/pkg/release/v1/release.go +++ b/pkg/release/v1/release.go @@ -36,7 +36,7 @@ type Release struct { Chart *chart.Chart `json:"chart,omitempty"` // Config is the set of extra Values added to the chart. // These values override the default values inside of the chart. - Config map[string]interface{} `json:"config,omitempty"` + Config map[string]any `json:"config,omitempty"` // Manifest is the string representation of the rendered template. Manifest string `json:"manifest,omitempty"` // Hooks are all of the hooks declared for this release. diff --git a/pkg/release/v1/util/kind_sorter.go b/pkg/release/v1/util/kind_sorter.go index bc074340f..01f1f801e 100644 --- a/pkg/release/v1/util/kind_sorter.go +++ b/pkg/release/v1/util/kind_sorter.go @@ -137,7 +137,7 @@ func sortHooksByKind(hooks []*release.Hook, ordering KindSortOrder) []*release.H return h } -func lessByKind(_ interface{}, _ interface{}, kindA string, kindB string, o KindSortOrder) bool { +func lessByKind(_ any, _ any, kindA string, kindB string, o KindSortOrder) bool { ordering := make(map[string]int, len(o)) for v, k := range o { ordering[k] = v diff --git a/pkg/repo/v1/index.go b/pkg/repo/v1/index.go index 7969d64e9..3dbdf7dfc 100644 --- a/pkg/repo/v1/index.go +++ b/pkg/repo/v1/index.go @@ -80,7 +80,7 @@ func (c ChartVersions) Less(a, b int) bool { // IndexFile represents the index file in a chart repository type IndexFile struct { // This is used ONLY for validation against chartmuseum's index files and is discarded after validation. - ServerInfo map[string]interface{} `json:"serverInfo,omitempty"` + ServerInfo map[string]any `json:"serverInfo,omitempty"` APIVersion string `json:"apiVersion"` Generated time.Time `json:"generated"` Entries map[string]ChartVersions `json:"entries"` @@ -270,7 +270,7 @@ func (i *IndexFile) Merge(f *IndexFile) { type ChartVersion struct { *chart.Metadata URLs []string `json:"urls"` - Created time.Time `json:"created,omitempty"` + Created time.Time `json:"created"` Removed bool `json:"removed,omitempty"` Digest string `json:"digest,omitempty"` @@ -391,7 +391,7 @@ func loadIndex(data []byte, source string) (*IndexFile, error) { // checking its validity as JSON. If the data is valid JSON, it will use the // `encoding/json` package to unmarshal it. Otherwise, it will use the // `sigs.k8s.io/yaml` package to unmarshal the YAML data. -func jsonOrYamlUnmarshal(b []byte, i interface{}) error { +func jsonOrYamlUnmarshal(b []byte, i any) error { if json.Valid(b) { return json.Unmarshal(b, i) } diff --git a/pkg/repo/v1/repotest/server.go b/pkg/repo/v1/repotest/server.go index 22b4c8060..bb8c2e0bf 100644 --- a/pkg/repo/v1/repotest/server.go +++ b/pkg/repo/v1/repotest/server.go @@ -189,7 +189,7 @@ func NewOCIServer(t *testing.T, dir string) (*OCIServer, error) { port := ln.Addr().(*net.TCPAddr).Port config.HTTP.Addr = ln.Addr().String() config.HTTP.DrainTimeout = time.Duration(10) * time.Second - config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}} + config.Storage = map[string]configuration.Parameters{"inmemory": map[string]any{}} config.Auth = configuration.Auth{ "htpasswd": configuration.Parameters{ "realm": "localhost", diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index 0055c095c..572342a20 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -576,7 +576,7 @@ func (test ReleaseTestData) ToRelease() *rspb.Release { } } -func assertErrNil(eh func(args ...interface{}), err error, message string) { +func assertErrNil(eh func(args ...any), err error, message string) { if err != nil { eh(fmt.Sprintf("%s: %q", message, err)) } diff --git a/pkg/strvals/literal_parser.go b/pkg/strvals/literal_parser.go index c2a824220..963558113 100644 --- a/pkg/strvals/literal_parser.go +++ b/pkg/strvals/literal_parser.go @@ -26,8 +26,8 @@ import ( // ParseLiteral parses a set line interpreting the value as a literal string. // // A set line is of the form name1=value1 -func ParseLiteral(s string) (map[string]interface{}, error) { - vals := map[string]interface{}{} +func ParseLiteral(s string) (map[string]any, error) { + vals := map[string]any{} scanner := bytes.NewBufferString(s) t := newLiteralParser(scanner, vals) err := t.parse() @@ -39,7 +39,7 @@ func ParseLiteral(s string) (map[string]interface{}, error) { // // If the strval string has a key that exists in dest, it overwrites the // dest version. -func ParseLiteralInto(s string, dest map[string]interface{}) error { +func ParseLiteralInto(s string, dest map[string]any) error { scanner := bytes.NewBufferString(s) t := newLiteralParser(scanner, dest) return t.parse() @@ -54,10 +54,10 @@ func ParseLiteralInto(s string, dest map[string]interface{}) error { // where data is the final parsed data from the parses with correct types type literalParser struct { sc *bytes.Buffer - data map[string]interface{} + data map[string]any } -func newLiteralParser(sc *bytes.Buffer, data map[string]interface{}) *literalParser { +func newLiteralParser(sc *bytes.Buffer, data map[string]any) *literalParser { return &literalParser{sc: sc, data: data} } @@ -88,7 +88,7 @@ func runesUntilLiteral(in io.RuneReader, stop map[rune]bool) ([]rune, rune, erro } } -func (t *literalParser) key(data map[string]interface{}, nestedNameLevel int) (reterr error) { +func (t *literalParser) key(data map[string]any, nestedNameLevel int) (reterr error) { defer func() { if r := recover(); r != nil { reterr = fmt.Errorf("unable to parse key: %s", r) @@ -120,9 +120,9 @@ func (t *literalParser) key(data map[string]interface{}, nestedNameLevel int) (r } // first, create or find the target map in the given data - inner := map[string]interface{}{} + inner := map[string]any{} if _, ok := data[string(key)]; ok { - inner = data[string(key)].(map[string]interface{}) + inner = data[string(key)].(map[string]any) } // recurse on sub-tree with remaining data @@ -144,9 +144,9 @@ func (t *literalParser) key(data map[string]interface{}, nestedNameLevel int) (r kk := string(key) // find or create target list - list := []interface{}{} + list := []any{} if _, ok := data[kk]; ok { - list = data[kk].([]interface{}) + list = data[kk].([]any) } // now we need to get the value after the ] @@ -169,7 +169,7 @@ func (t *literalParser) keyIndex() (int, error) { return strconv.Atoi(string(v)) } -func (t *literalParser) listItem(list []interface{}, i, nestedNameLevel int) ([]interface{}, error) { +func (t *literalParser) listItem(list []any, i, nestedNameLevel int) ([]any, error) { if i < 0 { return list, fmt.Errorf("negative %d index not allowed", i) } @@ -191,14 +191,14 @@ func (t *literalParser) listItem(list []interface{}, i, nestedNameLevel int) ([] case lastRune == '.': // we have a nested object. Send to t.key - inner := map[string]interface{}{} + inner := map[string]any{} if len(list) > i { var ok bool - inner, ok = list[i].(map[string]interface{}) + inner, ok = list[i].(map[string]any) if !ok { // We have indices out of order. Initialize empty value. - list[i] = map[string]interface{}{} - inner = list[i].(map[string]interface{}) + list[i] = map[string]any{} + inner = list[i].(map[string]any) } } @@ -215,12 +215,12 @@ func (t *literalParser) listItem(list []interface{}, i, nestedNameLevel int) ([] if err != nil { return list, fmt.Errorf("error parsing index: %w", err) } - var crtList []interface{} + var crtList []any if len(list) > i { // If nested list already exists, take the value of list to next cycle. existed := list[i] if existed != nil { - crtList = list[i].([]interface{}) + crtList = list[i].([]any) } } diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index 8eb761dce..cecaa2453 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -52,8 +52,8 @@ func ToYAML(s string) (string, error) { // Parse parses a set line. // // A set line is of the form name1=value1,name2=value2 -func Parse(s string) (map[string]interface{}, error) { - vals := map[string]interface{}{} +func Parse(s string) (map[string]any, error) { + vals := map[string]any{} scanner := bytes.NewBufferString(s) t := newParser(scanner, vals, false) err := t.parse() @@ -63,8 +63,8 @@ func Parse(s string) (map[string]interface{}, error) { // ParseString parses a set line and forces a string value. // // A set line is of the form name1=value1,name2=value2 -func ParseString(s string) (map[string]interface{}, error) { - vals := map[string]interface{}{} +func ParseString(s string) (map[string]any, error) { + vals := map[string]any{} scanner := bytes.NewBufferString(s) t := newParser(scanner, vals, true) err := t.parse() @@ -75,7 +75,7 @@ func ParseString(s string) (map[string]interface{}, error) { // // If the strval string has a key that exists in dest, it overwrites the // dest version. -func ParseInto(s string, dest map[string]interface{}) error { +func ParseInto(s string, dest map[string]any) error { scanner := bytes.NewBufferString(s) t := newParser(scanner, dest, false) return t.parse() @@ -87,8 +87,8 @@ func ParseInto(s string, dest map[string]interface{}) error { // // When the files at path1 and path2 contained "val1" and "val2" respectively, the set line is consumed as // name1=val1,name2=val2 -func ParseFile(s string, reader RunesValueReader) (map[string]interface{}, error) { - vals := map[string]interface{}{} +func ParseFile(s string, reader RunesValueReader) (map[string]any, error) { + vals := map[string]any{} scanner := bytes.NewBufferString(s) t := newFileParser(scanner, vals, reader) err := t.parse() @@ -98,7 +98,7 @@ func ParseFile(s string, reader RunesValueReader) (map[string]interface{}, error // ParseIntoString parses a strvals line and merges the result into dest. // // This method always returns a string as the value. -func ParseIntoString(s string, dest map[string]interface{}) error { +func ParseIntoString(s string, dest map[string]any) error { scanner := bytes.NewBufferString(s) t := newParser(scanner, dest, true) return t.parse() @@ -109,7 +109,7 @@ func ParseIntoString(s string, dest map[string]interface{}) error { // An empty val is treated as null. // // If a key exists in dest, the new value overwrites the dest version. -func ParseJSON(s string, dest map[string]interface{}) error { +func ParseJSON(s string, dest map[string]any) error { scanner := bytes.NewBufferString(s) t := newJSONParser(scanner, dest) return t.parse() @@ -118,7 +118,7 @@ func ParseJSON(s string, dest map[string]interface{}) error { // ParseIntoFile parses a filevals line and merges the result into dest. // // This method always returns a string as the value. -func ParseIntoFile(s string, dest map[string]interface{}, reader RunesValueReader) error { +func ParseIntoFile(s string, dest map[string]any, reader RunesValueReader) error { scanner := bytes.NewBufferString(s) t := newFileParser(scanner, dest, reader) return t.parse() @@ -126,7 +126,7 @@ func ParseIntoFile(s string, dest map[string]interface{}, reader RunesValueReade // RunesValueReader is a function that takes the given value (a slice of runes) // and returns the parsed value -type RunesValueReader func([]rune) (interface{}, error) +type RunesValueReader func([]rune) (any, error) // parser is a simple parser that takes a strvals line and parses it into a // map representation. @@ -135,23 +135,23 @@ type RunesValueReader func([]rune) (interface{}, error) // where data is the final parsed data from the parses with correct types type parser struct { sc *bytes.Buffer - data map[string]interface{} + data map[string]any reader RunesValueReader isjsonval bool } -func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *parser { - stringConverter := func(rs []rune) (interface{}, error) { +func newParser(sc *bytes.Buffer, data map[string]any, stringBool bool) *parser { + stringConverter := func(rs []rune) (any, error) { return typedVal(rs, stringBool), nil } return &parser{sc: sc, data: data, reader: stringConverter} } -func newJSONParser(sc *bytes.Buffer, data map[string]interface{}) *parser { +func newJSONParser(sc *bytes.Buffer, data map[string]any) *parser { return &parser{sc: sc, data: data, reader: nil, isjsonval: true} } -func newFileParser(sc *bytes.Buffer, data map[string]interface{}, reader RunesValueReader) *parser { +func newFileParser(sc *bytes.Buffer, data map[string]any, reader RunesValueReader) *parser { return &parser{sc: sc, data: data, reader: reader} } @@ -176,7 +176,7 @@ func runeSet(r []rune) map[rune]bool { return s } -func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr error) { +func (t *parser) key(data map[string]any, nestedNameLevel int) (reterr error) { defer func() { if r := recover(); r != nil { reterr = fmt.Errorf("unable to parse key: %s", r) @@ -200,9 +200,9 @@ func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr e } kk := string(k) // Find or create target list - list := []interface{}{} + list := []any{} if _, ok := data[kk]; ok { - list = data[kk].([]interface{}) + list = data[kk].([]any) } // Now we need to get the value after the ]. @@ -224,7 +224,7 @@ func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr e // Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded, // we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we // discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset). - var jsonval interface{} + var jsonval any dec := json.NewDecoder(strings.NewReader(t.sc.String())) if err = dec.Decode(&jsonval); err != nil { return err @@ -270,9 +270,9 @@ func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr e } // First, create or find the target map. - inner := map[string]interface{}{} + inner := map[string]any{} if _, ok := data[string(k)]; ok { - inner = data[string(k)].(map[string]interface{}) + inner = data[string(k)].(map[string]any) } // Recurse @@ -288,7 +288,7 @@ func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr e } } -func set(data map[string]interface{}, key string, val interface{}) { +func set(data map[string]any, key string, val any) { // If key is empty, don't set it. if len(key) == 0 { return @@ -296,7 +296,7 @@ func set(data map[string]interface{}, key string, val interface{}) { data[key] = val } -func setIndex(list []interface{}, index int, val interface{}) (l2 []interface{}, err error) { +func setIndex(list []any, index int, val any) (l2 []any, err error) { // There are possible index values that are out of range on a target system // causing a panic. This will catch the panic and return an error instead. // The value of the index that causes a panic varies from system to system. @@ -313,7 +313,7 @@ func setIndex(list []interface{}, index int, val interface{}) (l2 []interface{}, return list, fmt.Errorf("index of %d is greater than maximum supported index of %d", index, MaxIndex) } if len(list) <= index { - newlist := make([]interface{}, index+1) + newlist := make([]any, index+1) copy(newlist, list) list = newlist } @@ -333,7 +333,7 @@ func (t *parser) keyIndex() (int, error) { } -func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interface{}, error) { +func (t *parser) listItem(list []any, i, nestedNameLevel int) ([]any, error) { if i < 0 { return list, fmt.Errorf("negative %d index not allowed", i) } @@ -357,7 +357,7 @@ func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interfa // Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded, // we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we // discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset). - var jsonval interface{} + var jsonval any dec := json.NewDecoder(strings.NewReader(t.sc.String())) if err = dec.Decode(&jsonval); err != nil { return list, err @@ -397,12 +397,12 @@ func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interfa if err != nil { return list, fmt.Errorf("error parsing index: %w", err) } - var crtList []interface{} + var crtList []any if len(list) > i { // If nested list already exists, take the value of list to next cycle. existed := list[i] if existed != nil { - crtList = list[i].([]interface{}) + crtList = list[i].([]any) } } // Now we need to get the value after the ]. @@ -413,14 +413,14 @@ func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interfa return setIndex(list, i, list2) case last == '.': // We have a nested object. Send to t.key - inner := map[string]interface{}{} + inner := map[string]any{} if len(list) > i { var ok bool - inner, ok = list[i].(map[string]interface{}) + inner, ok = list[i].(map[string]any) if !ok { // We have indices out of order. Initialize empty value. - list[i] = map[string]interface{}{} - inner = list[i].(map[string]interface{}) + list[i] = map[string]any{} + inner = list[i].(map[string]any) } } @@ -463,18 +463,18 @@ func (t *parser) val() ([]rune, error) { return v, err } -func (t *parser) valList() ([]interface{}, error) { +func (t *parser) valList() ([]any, error) { r, _, e := t.sc.ReadRune() if e != nil { - return []interface{}{}, e + return []any{}, e } if r != '{' { t.sc.UnreadRune() - return []interface{}{}, ErrNotList + return []any{}, ErrNotList } - list := []interface{}{} + list := []any{} stop := runeSet([]rune{',', '}'}) for { switch rs, last, err := runesUntil(t.sc, stop); { @@ -526,7 +526,7 @@ func inMap(k rune, m map[rune]bool) bool { return ok } -func typedVal(v []rune, st bool) interface{} { +func typedVal(v []rune, st bool) any { val := string(v) if st { From 956c7245c346fc304c24ace930dada5f2c99f2b1 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Mon, 23 Feb 2026 15:58:30 -0800 Subject: [PATCH 190/571] chore: Improve `AGENTS.md` Signed-off-by: George Jenkins --- AGENTS.md | 74 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 17 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d2904a9da..a2e192f74 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,48 +1,88 @@ # AGENTS.md ## Overview -Helm is a package manager for Kubernetes written in Go, supporting v3 (stable) and v4 (unstable) APIs. -## Build & Test +Helm is a package manager for Kubernetes written in Go. It enables users to define, install, and upgrade complex Kubernetes applications using charts. +This document provides an overview of the codebase structure, development guidelines, and key patterns for contributors. + +The codebase supports both an SDK for advanced users, and a CLI for direct end user usage. + +The project currently supports Helm v3 and Helm v4 versions, based on the `dev-v3` and `main` branches respectively. + +## Build and test + ```bash make build # Build binary make test # Run all tests (style + unit) make test-unit # Unit tests only make test-coverage # With coverage -make test-style # Linting -golangci-lint run # Direct linting +make test-style # Linting (wraps golangci-lint) go test -run TestName # Specific test ``` -## Code Structure -- `/cmd/helm/` - CLI entry point (Cobra-based) -- `/pkg/` - Public API +## Code structure + +Major packages: + +- `cmd/helm/` - CLI entry point, wires CLI flags to `pkg/cmd/` commands +- `pkg/` - Public API - `action/` - Core operations (install, upgrade, rollback) + - `cmd/` - Cobra command implementations bridging CLI flags to `pkg/action/` - `chart/v2/` - Stable chart format - `engine/` - Template rendering (Go templates + Sprig) + - `kube/` - Kubernetes client abstraction layer - `registry/` - OCI support + - `release/` - Release types and interfaces (`v1/`, `common/`) + - `repo/` - Chart repository indexing and interaction - `storage/` - Release backends (Secrets/ConfigMaps/SQL) -- `/internal/` - Private implementation +- `internal/` - Private implementations - `chart/v3/` - Next-gen chart format + - `release/v2/` - Release package for chart v3 support + +## Development + +### Compatibility + +Changes are required to maintain backward compatibility as described in [HIP-0004: Document backwards-compatibility rules](https://github.com/helm/community/blob/main/hips/hip-0004.md). + +Typically this means that: -## Development Guidelines +- the signatures of public APIs, i.e., those in the `pkg/` directory should not change +- CLI commands and parameters should not be removed or changed in a way that would break existing scripts or workflows +- functional behaviour (as implied or documented) must not be modified in a way that would break existing users' expectations + +An exception to the above is where incompatible changes are needed to fix a security vulnerability, where minimal breaking changes may be made to address the issue. + +### Code standards -### Code Standards - Use table-driven tests with testify - Golden files in `testdata/` for complex output - Mock Kubernetes clients for action tests - All commits must include DCO sign-off: `git commit -s` ### Branching -- `main` - Helm v4 development -- `dev-v3` - Helm v3 stable (backport from main) -### Dependencies +Standard workflow is for PR development changes to the `main` branch. Minor release branches are cut from `main`, then maintained for critical fixes via patch releases. +Bug and security fixes are also backported to `dev-v3` where applicable. + +Development branches: + +- `main` - Helm v4 +- `dev-v3` - Helm v3 (backport security and bugfixes from main) + +Release branches: + +- `release-v3.X` - Release branches for v3.X versions +- `release-v4.X` - Release branches for v4.X versions + +### Major dependencies + - `k8s.io/client-go` - Kubernetes interaction - `github.com/spf13/cobra` - CLI framework - `github.com/Masterminds/sprig` - Template functions -### Key Patterns -- **Actions**: Operations in `/pkg/action/` use shared Configuration -- **Dual Chart Support**: v2 (stable) in `/pkg/`, v3 (dev) in `/internal/` -- **Storage Abstraction**: Pluggable release storage backends +### Key patterns + +- **Actions**: High-level operations live in `pkg/action/`, typically using a shared Configuration +- **Chart versions**: Charts v2 (stable) in `pkg/chart/v2`, v3 (under development) in `internal/chart/v3` +- **Plugins and extensibility**: Enabling additional functionality via plugins and extension points, such as custom template functions or storage backends is preferred over incorporating into Helm's codebase From b6673174220a2633fe97b5cd70a8386b79103464 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Mon, 23 Feb 2026 19:17:02 -0800 Subject: [PATCH 191/571] fixup `strings.Cut` variables Signed-off-by: George Jenkins --- pkg/engine/engine.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index e39e63845..0ce084316 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -410,9 +410,7 @@ func parseTemplateSimpleErrorString(remainder string) (TraceableError, bool) { // Executing form: ": executing \"\" at <>: [ template:...]" // Matches https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=141 func parseTemplateExecutingAtErrorType(remainder string) (TraceableError, bool) { - if before, after, ok := strings.Cut(remainder, ": executing "); ok { - templateName := before - after := after + if templateName, after, found := strings.Cut(remainder, ": executing "); found { if len(after) == 0 || after[0] != '"' { return TraceableError{}, false } @@ -431,12 +429,10 @@ func parseTemplateExecutingAtErrorType(remainder string) (TraceableError, bool) return TraceableError{}, false } afterAt := afterFunc[len(atPrefix):] - before, after0, ok := strings.Cut(afterAt, ">: ") - if !ok { + locationName, errMsg, found := strings.Cut(afterAt, ">: ") + if !found { return TraceableError{}, false } - locationName := before - errMsg := after0 // trim chained next error starting with space + "template:" if present if cut := strings.Index(errMsg, " template:"); cut != -1 { From 300f71b1ebb1b383241a1806a813c6f30a8ff382 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:28:07 +0100 Subject: [PATCH 192/571] feat(history): add rollback revision column to helm history output Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- internal/release/v2/info.go | 27 +++-- pkg/action/rollback.go | 9 +- pkg/action/rollback_test.go | 48 +++++++++ pkg/cmd/history.go | 67 ++++++------ pkg/cmd/history_test.go | 120 ++++++++++++++++++++++ pkg/cmd/testdata/output/history-limit.txt | 6 +- pkg/cmd/testdata/output/history.txt | 10 +- pkg/release/v1/info.go | 27 +++-- 8 files changed, 251 insertions(+), 63 deletions(-) diff --git a/internal/release/v2/info.go b/internal/release/v2/info.go index 038f19409..04a9580fe 100644 --- a/internal/release/v2/info.go +++ b/internal/release/v2/info.go @@ -36,6 +36,8 @@ type Info struct { Description string `json:"description,omitempty"` // Status is the current state of the release Status common.Status `json:"status,omitempty"` + // RollbackRevision is the revision that was rolled back to. Zero means not a rollback. + RollbackRevision int `json:"rollback_revision,omitempty"` // Contains the rendered templates/NOTES.txt if available Notes string `json:"notes,omitempty"` // Contains the deployed resources information @@ -44,13 +46,14 @@ type Info struct { // infoJSON is used for custom JSON marshaling/unmarshaling type infoJSON struct { - FirstDeployed *time.Time `json:"first_deployed,omitempty"` - LastDeployed *time.Time `json:"last_deployed,omitempty"` - Deleted *time.Time `json:"deleted,omitempty"` - Description string `json:"description,omitempty"` - Status common.Status `json:"status,omitempty"` - Notes string `json:"notes,omitempty"` - Resources map[string][]runtime.Object `json:"resources,omitempty"` + FirstDeployed *time.Time `json:"first_deployed,omitempty"` + LastDeployed *time.Time `json:"last_deployed,omitempty"` + Deleted *time.Time `json:"deleted,omitempty"` + Description string `json:"description,omitempty"` + Status common.Status `json:"status,omitempty"` + RollbackRevision int `json:"rollback_revision,omitempty"` + Notes string `json:"notes,omitempty"` + Resources map[string][]runtime.Object `json:"resources,omitempty"` } // UnmarshalJSON implements the json.Unmarshaler interface. @@ -95,6 +98,7 @@ func (i *Info) UnmarshalJSON(data []byte) error { } i.Description = tmp.Description i.Status = tmp.Status + i.RollbackRevision = tmp.RollbackRevision i.Notes = tmp.Notes i.Resources = tmp.Resources @@ -105,10 +109,11 @@ func (i *Info) UnmarshalJSON(data []byte) error { // It omits zero-value time fields from the JSON output. func (i Info) MarshalJSON() ([]byte, error) { tmp := infoJSON{ - Description: i.Description, - Status: i.Status, - Notes: i.Notes, - Resources: i.Resources, + Description: i.Description, + Status: i.Status, + RollbackRevision: i.RollbackRevision, + Notes: i.Notes, + Resources: i.Resources, } if !i.FirstDeployed.IsZero() { diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 459569781..6fc449c30 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -176,10 +176,11 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele Chart: previousRelease.Chart, Config: previousRelease.Config, Info: &release.Info{ - FirstDeployed: currentRelease.Info.FirstDeployed, - LastDeployed: time.Now(), - Status: common.StatusPendingRollback, - Notes: previousRelease.Info.Notes, + FirstDeployed: currentRelease.Info.FirstDeployed, + LastDeployed: time.Now(), + Status: common.StatusPendingRollback, + Notes: previousRelease.Info.Notes, + RollbackRevision: previousVersion, // Because we lose the reference to previous version elsewhere, we set the // message here, and only override it later if we experience failure. Description: fmt.Sprintf("Rollback to %d", previousVersion), diff --git a/pkg/action/rollback_test.go b/pkg/action/rollback_test.go index deb6c7c80..b34adda91 100644 --- a/pkg/action/rollback_test.go +++ b/pkg/action/rollback_test.go @@ -83,3 +83,51 @@ func TestRollback_WaitOptionsPassedDownstream(t *testing.T) { // Verify that WaitOptions were passed to GetWaiter is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") } + +func TestRollbackSetsRollbackRevision(t *testing.T) { + config := actionConfigFixture(t) + + rel1 := releaseStub() + rel1.Name = "rollback-rev-test" + rel1.Version = 1 + rel1.Info.Status = "superseded" + rel1.ApplyMethod = "csa" + require.NoError(t, config.Releases.Create(rel1)) + + rel2 := releaseStub() + rel2.Name = "rollback-rev-test" + rel2.Version = 2 + rel2.Info.Status = "deployed" + rel2.ApplyMethod = "csa" + require.NoError(t, config.Releases.Create(rel2)) + + client := NewRollback(config) + client.Version = 1 + client.ServerSideApply = "auto" + + require.NoError(t, client.Run("rollback-rev-test")) + + reli, err := config.Releases.Get("rollback-rev-test", 3) + require.NoError(t, err) + rel, err := releaserToV1Release(reli) + require.NoError(t, err) + + assert.Equal(t, 1, rel.Info.RollbackRevision) + assert.Equal(t, "Rollback to 1", rel.Info.Description) +} + +func TestRollbackRevisionZeroForNonRollback(t *testing.T) { + config := actionConfigFixture(t) + + rel := releaseStub() + rel.Name = "non-rollback" + rel.Info.Status = "deployed" + require.NoError(t, config.Releases.Create(rel)) + + reli, err := config.Releases.Get("non-rollback", 1) + require.NoError(t, err) + r, err := releaserToV1Release(reli) + require.NoError(t, err) + + assert.Equal(t, 0, r.Info.RollbackRevision) +} diff --git a/pkg/cmd/history.go b/pkg/cmd/history.go index b294a9da7..eb82da684 100644 --- a/pkg/cmd/history.go +++ b/pkg/cmd/history.go @@ -43,11 +43,11 @@ configures the maximum length of the revision list returned. The historical release set is printed as a formatted table, e.g: $ helm history angry-bird - REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION - 1 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Initial install - 2 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Upgraded successfully - 3 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Rolled back to 2 - 4 Mon Oct 3 10:15:13 2016 deployed alpine-0.1.0 1.0 Upgraded successfully + REVISION UPDATED STATUS CHART APP VERSION ROLLBACK DESCRIPTION + 1 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Initial install + 2 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Upgraded successfully + 3 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 2 Rolled back to 2 + 4 Mon Oct 3 10:15:13 2016 deployed alpine-0.1.0 1.0 Upgraded successfully ` func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { @@ -84,22 +84,24 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } type releaseInfo struct { - Revision int `json:"revision"` - Updated time.Time `json:"updated,omitzero"` - Status string `json:"status"` - Chart string `json:"chart"` - AppVersion string `json:"app_version"` - Description string `json:"description"` + Revision int `json:"revision"` + Updated time.Time `json:"updated,omitzero"` + Status string `json:"status"` + Chart string `json:"chart"` + AppVersion string `json:"app_version"` + RollbackRevision int `json:"rollback_revision,omitempty"` + Description string `json:"description"` } // releaseInfoJSON is used for custom JSON marshaling/unmarshaling type releaseInfoJSON struct { - Revision int `json:"revision"` - Updated *time.Time `json:"updated,omitempty"` - Status string `json:"status"` - Chart string `json:"chart"` - AppVersion string `json:"app_version"` - Description string `json:"description"` + Revision int `json:"revision"` + Updated *time.Time `json:"updated,omitempty"` + Status string `json:"status"` + Chart string `json:"chart"` + AppVersion string `json:"app_version"` + RollbackRevision int `json:"rollback_revision,omitempty"` + Description string `json:"description"` } // UnmarshalJSON implements the json.Unmarshaler interface. @@ -138,6 +140,7 @@ func (r *releaseInfo) UnmarshalJSON(data []byte) error { r.Status = tmp.Status r.Chart = tmp.Chart r.AppVersion = tmp.AppVersion + r.RollbackRevision = tmp.RollbackRevision r.Description = tmp.Description return nil @@ -147,11 +150,12 @@ func (r *releaseInfo) UnmarshalJSON(data []byte) error { // It omits zero-value time fields from the JSON output. func (r releaseInfo) MarshalJSON() ([]byte, error) { tmp := releaseInfoJSON{ - Revision: r.Revision, - Status: r.Status, - Chart: r.Chart, - AppVersion: r.AppVersion, - Description: r.Description, + Revision: r.Revision, + Status: r.Status, + Chart: r.Chart, + AppVersion: r.AppVersion, + RollbackRevision: r.RollbackRevision, + Description: r.Description, } if !r.Updated.IsZero() { @@ -173,9 +177,13 @@ func (r releaseHistory) WriteYAML(out io.Writer) error { func (r releaseHistory) WriteTable(out io.Writer) error { tbl := uitable.New() - tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "DESCRIPTION") + tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "ROLLBACK", "DESCRIPTION") for _, item := range r { - tbl.AddRow(item.Revision, item.Updated.Format(time.ANSIC), item.Status, item.Chart, item.AppVersion, item.Description) + rollback := "" + if item.RollbackRevision > 0 { + rollback = strconv.Itoa(item.RollbackRevision) + } + tbl.AddRow(item.Revision, item.Updated.Format(time.ANSIC), item.Status, item.Chart, item.AppVersion, rollback, item.Description) } return output.EncodeTable(out, tbl) } @@ -216,11 +224,12 @@ func getReleaseHistory(rls []*release.Release) (history releaseHistory) { a := formatAppVersion(r.Chart) rInfo := releaseInfo{ - Revision: v, - Status: s, - Chart: c, - AppVersion: a, - Description: d, + Revision: v, + Status: s, + Chart: c, + AppVersion: a, + RollbackRevision: r.Info.RollbackRevision, + Description: d, } if !r.Info.LastDeployed.IsZero() { rInfo.Updated = r.Info.LastDeployed diff --git a/pkg/cmd/history_test.go b/pkg/cmd/history_test.go index d8adc2d19..86fd47bf1 100644 --- a/pkg/cmd/history_test.go +++ b/pkg/cmd/history_test.go @@ -27,6 +27,8 @@ import ( "helm.sh/helm/v4/pkg/release/common" release "helm.sh/helm/v4/pkg/release/v1" + + chart "helm.sh/helm/v4/pkg/chart/v2" ) func TestHistoryCmd(t *testing.T) { @@ -76,6 +78,72 @@ func TestHistoryCmd(t *testing.T) { runTestCmd(t, tests) } +func TestHistoryWithRollback(t *testing.T) { + date := time.Unix(242085845, 0).UTC() + ch := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "foo", + Version: "0.1.0-beta.1", + AppVersion: "1.0", + }, + } + + rels := []*release.Release{ + { + Name: "angry-bird", + Version: 1, + Info: &release.Info{ + FirstDeployed: date, + LastDeployed: date, + Status: common.StatusSuperseded, + Description: "Install complete", + }, + Chart: ch, + }, + { + Name: "angry-bird", + Version: 2, + Info: &release.Info{ + FirstDeployed: date, + LastDeployed: date, + Status: common.StatusSuperseded, + Description: "Upgrade complete", + }, + Chart: ch, + }, + { + Name: "angry-bird", + Version: 3, + Info: &release.Info{ + FirstDeployed: date, + LastDeployed: date, + Status: common.StatusDeployed, + RollbackRevision: 1, + Description: "Rollback to 1", + }, + Chart: ch, + }, + } + + tests := []cmdTestCase{{ + name: "history with rollback revision", + cmd: "history angry-bird", + rels: rels, + golden: "output/history-with-rollback.txt", + }, { + name: "history with rollback revision json", + cmd: "history angry-bird --output json", + rels: rels, + golden: "output/history-with-rollback.json", + }, { + name: "history with rollback revision yaml", + cmd: "history angry-bird --output yaml", + rels: rels, + golden: "output/history-with-rollback.yaml", + }} + runTestCmd(t, tests) +} + func TestHistoryOutputCompletion(t *testing.T) { outputFlagCompletionTest(t, "history") } @@ -173,6 +241,31 @@ func TestReleaseInfoMarshalJSON(t *testing.T) { }, expected: `{"revision":0,"updated":"2025-10-08T12:00:00Z","status":"failed","chart":"mychart-1.0.0","app_version":"1.0.0","description":"Install failed"}`, }, + { + name: "with rollback revision", + info: releaseInfo{ + Revision: 3, + Updated: updated, + Status: "deployed", + Chart: "mychart-1.0.0", + AppVersion: "1.0.0", + RollbackRevision: 1, + Description: "Rollback to 1", + }, + expected: `{"revision":3,"updated":"2025-10-08T12:00:00Z","status":"deployed","chart":"mychart-1.0.0","app_version":"1.0.0","rollback_revision":1,"description":"Rollback to 1"}`, + }, + { + name: "without rollback revision", + info: releaseInfo{ + Revision: 1, + Updated: updated, + Status: "deployed", + Chart: "mychart-1.0.0", + AppVersion: "1.0.0", + Description: "Initial install", + }, + expected: `{"revision":1,"updated":"2025-10-08T12:00:00Z","status":"deployed","chart":"mychart-1.0.0","app_version":"1.0.0","description":"Initial install"}`, + }, } for _, tt := range tests { @@ -255,6 +348,31 @@ func TestReleaseInfoUnmarshalJSON(t *testing.T) { Description: "Installing", }, }, + { + name: "with rollback revision", + input: `{"revision":3,"updated":"2025-10-08T12:00:00Z","status":"deployed","chart":"mychart-1.0.0","app_version":"1.0.0","rollback_revision":1,"description":"Rollback to 1"}`, + expected: releaseInfo{ + Revision: 3, + Updated: updated, + Status: "deployed", + Chart: "mychart-1.0.0", + AppVersion: "1.0.0", + RollbackRevision: 1, + Description: "Rollback to 1", + }, + }, + { + name: "without rollback revision field", + input: `{"revision":1,"updated":"2025-10-08T12:00:00Z","status":"deployed","chart":"mychart-1.0.0","app_version":"1.0.0","description":"Install"}`, + expected: releaseInfo{ + Revision: 1, + Updated: updated, + Status: "deployed", + Chart: "mychart-1.0.0", + AppVersion: "1.0.0", + Description: "Install", + }, + }, } for _, tt := range tests { @@ -271,6 +389,7 @@ func TestReleaseInfoUnmarshalJSON(t *testing.T) { assert.Equal(t, tt.expected.Status, info.Status) assert.Equal(t, tt.expected.Chart, info.Chart) assert.Equal(t, tt.expected.AppVersion, info.AppVersion) + assert.Equal(t, tt.expected.RollbackRevision, info.RollbackRevision) assert.Equal(t, tt.expected.Description, info.Description) }) } @@ -300,6 +419,7 @@ func TestReleaseInfoRoundTrip(t *testing.T) { assert.Equal(t, original.Status, decoded.Status) assert.Equal(t, original.Chart, decoded.Chart) assert.Equal(t, original.AppVersion, decoded.AppVersion) + assert.Equal(t, original.RollbackRevision, decoded.RollbackRevision) assert.Equal(t, original.Description, decoded.Description) } diff --git a/pkg/cmd/testdata/output/history-limit.txt b/pkg/cmd/testdata/output/history-limit.txt index aee0fadb2..f6f059fb9 100644 --- a/pkg/cmd/testdata/output/history-limit.txt +++ b/pkg/cmd/testdata/output/history-limit.txt @@ -1,3 +1,3 @@ -REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION -3 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock -4 Fri Sep 2 22:04:05 1977 deployed foo-0.1.0-beta.1 1.0 Release mock +REVISION UPDATED STATUS CHART APP VERSION ROLLBACK DESCRIPTION +3 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock +4 Fri Sep 2 22:04:05 1977 deployed foo-0.1.0-beta.1 1.0 Release mock diff --git a/pkg/cmd/testdata/output/history.txt b/pkg/cmd/testdata/output/history.txt index 2a5d69c11..4a51add7f 100644 --- a/pkg/cmd/testdata/output/history.txt +++ b/pkg/cmd/testdata/output/history.txt @@ -1,5 +1,5 @@ -REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION -1 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock -2 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock -3 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock -4 Fri Sep 2 22:04:05 1977 deployed foo-0.1.0-beta.1 1.0 Release mock +REVISION UPDATED STATUS CHART APP VERSION ROLLBACK DESCRIPTION +1 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock +2 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock +3 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock +4 Fri Sep 2 22:04:05 1977 deployed foo-0.1.0-beta.1 1.0 Release mock diff --git a/pkg/release/v1/info.go b/pkg/release/v1/info.go index f895fdf6c..78f9a22fb 100644 --- a/pkg/release/v1/info.go +++ b/pkg/release/v1/info.go @@ -36,6 +36,8 @@ type Info struct { Description string `json:"description,omitempty"` // Status is the current state of the release Status common.Status `json:"status,omitempty"` + // RollbackRevision is the revision that was rolled back to. Zero means not a rollback. + RollbackRevision int `json:"rollback_revision,omitempty"` // Contains the rendered templates/NOTES.txt if available Notes string `json:"notes,omitempty"` // Contains the deployed resources information @@ -44,13 +46,14 @@ type Info struct { // infoJSON is used for custom JSON marshaling/unmarshaling type infoJSON struct { - FirstDeployed *time.Time `json:"first_deployed,omitempty"` - LastDeployed *time.Time `json:"last_deployed,omitempty"` - Deleted *time.Time `json:"deleted,omitempty"` - Description string `json:"description,omitempty"` - Status common.Status `json:"status,omitempty"` - Notes string `json:"notes,omitempty"` - Resources map[string][]runtime.Object `json:"resources,omitempty"` + FirstDeployed *time.Time `json:"first_deployed,omitempty"` + LastDeployed *time.Time `json:"last_deployed,omitempty"` + Deleted *time.Time `json:"deleted,omitempty"` + Description string `json:"description,omitempty"` + Status common.Status `json:"status,omitempty"` + RollbackRevision int `json:"rollback_revision,omitempty"` + Notes string `json:"notes,omitempty"` + Resources map[string][]runtime.Object `json:"resources,omitempty"` } // UnmarshalJSON implements the json.Unmarshaler interface. @@ -95,6 +98,7 @@ func (i *Info) UnmarshalJSON(data []byte) error { } i.Description = tmp.Description i.Status = tmp.Status + i.RollbackRevision = tmp.RollbackRevision i.Notes = tmp.Notes i.Resources = tmp.Resources @@ -105,10 +109,11 @@ func (i *Info) UnmarshalJSON(data []byte) error { // It omits zero-value time fields from the JSON output. func (i Info) MarshalJSON() ([]byte, error) { tmp := infoJSON{ - Description: i.Description, - Status: i.Status, - Notes: i.Notes, - Resources: i.Resources, + Description: i.Description, + Status: i.Status, + RollbackRevision: i.RollbackRevision, + Notes: i.Notes, + Resources: i.Resources, } if !i.FirstDeployed.IsZero() { From d52b489459d51d67d9db877f169bd09273b6cd6c Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Tue, 24 Feb 2026 09:35:22 +0100 Subject: [PATCH 193/571] Added missing pkg/cmd/testdata/output files Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- .../output/history-with-rollback.json | 1 + .../testdata/output/history-with-rollback.txt | 4 ++++ .../output/history-with-rollback.yaml | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 pkg/cmd/testdata/output/history-with-rollback.json create mode 100644 pkg/cmd/testdata/output/history-with-rollback.txt create mode 100644 pkg/cmd/testdata/output/history-with-rollback.yaml diff --git a/pkg/cmd/testdata/output/history-with-rollback.json b/pkg/cmd/testdata/output/history-with-rollback.json new file mode 100644 index 000000000..b4175b062 --- /dev/null +++ b/pkg/cmd/testdata/output/history-with-rollback.json @@ -0,0 +1 @@ +[{"revision":1,"updated":"1977-09-02T22:04:05Z","status":"superseded","chart":"foo-0.1.0-beta.1","app_version":"1.0","description":"Install complete"},{"revision":2,"updated":"1977-09-02T22:04:05Z","status":"superseded","chart":"foo-0.1.0-beta.1","app_version":"1.0","description":"Upgrade complete"},{"revision":3,"updated":"1977-09-02T22:04:05Z","status":"deployed","chart":"foo-0.1.0-beta.1","app_version":"1.0","rollback_revision":1,"description":"Rollback to 1"}] diff --git a/pkg/cmd/testdata/output/history-with-rollback.txt b/pkg/cmd/testdata/output/history-with-rollback.txt new file mode 100644 index 000000000..c6dd62b8a --- /dev/null +++ b/pkg/cmd/testdata/output/history-with-rollback.txt @@ -0,0 +1,4 @@ +REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION +1 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Install complete +2 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Upgrade complete +3 Fri Sep 2 22:04:05 1977 deployed foo-0.1.0-beta.1 1.0 Rollback to 1 diff --git a/pkg/cmd/testdata/output/history-with-rollback.yaml b/pkg/cmd/testdata/output/history-with-rollback.yaml new file mode 100644 index 000000000..19dc681c4 --- /dev/null +++ b/pkg/cmd/testdata/output/history-with-rollback.yaml @@ -0,0 +1,19 @@ +- app_version: "1.0" + chart: foo-0.1.0-beta.1 + description: Install complete + revision: 1 + status: superseded + updated: "1977-09-02T22:04:05Z" +- app_version: "1.0" + chart: foo-0.1.0-beta.1 + description: Upgrade complete + revision: 2 + status: superseded + updated: "1977-09-02T22:04:05Z" +- app_version: "1.0" + chart: foo-0.1.0-beta.1 + description: Rollback to 1 + revision: 3 + rollback_revision: 1 + status: deployed + updated: "1977-09-02T22:04:05Z" From 5b6c6bbfc7ca9850c69d3823ca1e21b445e75c0d Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Wed, 25 Feb 2026 07:47:53 +0100 Subject: [PATCH 194/571] fix: enable nolinlint linter Signed-off-by: Matthieu MOREL --- .golangci.yml | 8 ++++++-- internal/chart/v3/util/chartfile_test.go | 2 +- internal/plugin/signing_info.go | 2 +- pkg/chart/v2/util/chartfile_test.go | 4 ++-- pkg/getter/httpgetter_test.go | 20 ++++++++++---------- pkg/provenance/sign.go | 6 +++--- pkg/provenance/sign_test.go | 4 ++-- pkg/repo/v1/repo_test.go | 4 ++-- 8 files changed, 27 insertions(+), 23 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 7eca135e5..d808421a0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -25,6 +25,7 @@ linters: - ineffassign - misspell - nakedret + - nolintlint - revive - sloglint - staticcheck @@ -67,6 +68,9 @@ linters: dupl: threshold: 400 + exhaustive: + default-signifies-exhaustive: true + gomodguard: blocked: modules: @@ -74,8 +78,8 @@ linters: recommendations: - github.com/evanphx/json-patch/v5 - exhaustive: - default-signifies-exhaustive: true + nolintlint: + require-specific: true run: timeout: 10m diff --git a/internal/chart/v3/util/chartfile_test.go b/internal/chart/v3/util/chartfile_test.go index c3d19c381..579313f16 100644 --- a/internal/chart/v3/util/chartfile_test.go +++ b/internal/chart/v3/util/chartfile_test.go @@ -35,7 +35,7 @@ func TestLoadChartfile(t *testing.T) { func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { t.Helper() - if f == nil { //nolint:staticcheck + if f == nil { t.Fatal("Failed verifyChartfile because f is nil") } diff --git a/internal/plugin/signing_info.go b/internal/plugin/signing_info.go index 61ee9cd15..ff27cc02d 100644 --- a/internal/plugin/signing_info.go +++ b/internal/plugin/signing_info.go @@ -23,7 +23,7 @@ import ( "path/filepath" "strings" - "github.com/ProtonMail/go-crypto/openpgp/clearsign" //nolint + "github.com/ProtonMail/go-crypto/openpgp/clearsign" "helm.sh/helm/v4/pkg/helmpath" ) diff --git a/pkg/chart/v2/util/chartfile_test.go b/pkg/chart/v2/util/chartfile_test.go index 00c530b8a..c360b523e 100644 --- a/pkg/chart/v2/util/chartfile_test.go +++ b/pkg/chart/v2/util/chartfile_test.go @@ -35,11 +35,11 @@ func TestLoadChartfile(t *testing.T) { func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { t.Helper() - if f == nil { //nolint:staticcheck + if f == nil { t.Fatal("Failed verifyChartfile because f is nil") } - if f.APIVersion != chart.APIVersionV1 { //nolint:staticcheck + if f.APIVersion != chart.APIVersionV1 { t.Errorf("Expected API Version %q, got %q", chart.APIVersionV1, f.APIVersion) } diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index 3047ed6f6..e7c3f3cb1 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -583,10 +583,10 @@ func verifyInsecureSkipVerify(t *testing.T, g *HTTPGetter, caseName string, expe t.Fatal(err) } - if returnVal == nil { //nolint:staticcheck + if returnVal == nil { t.Fatalf("Expected non nil value for http client") } - transport := (returnVal.Transport).(*http.Transport) //nolint:staticcheck + transport := (returnVal.Transport).(*http.Transport) gotValue := false if transport.TLSClientConfig != nil { gotValue = transport.TLSClientConfig.InsecureSkipVerify @@ -607,11 +607,11 @@ func TestDefaultHTTPTransportReuse(t *testing.T) { t.Fatal(err) } - if httpClient1 == nil { //nolint:staticcheck + if httpClient1 == nil { t.Fatalf("Expected non nil value for http client") } - transport1 := (httpClient1.Transport).(*http.Transport) //nolint:staticcheck + transport1 := (httpClient1.Transport).(*http.Transport) httpClient2, err := g.httpClient(g.opts) @@ -619,11 +619,11 @@ func TestDefaultHTTPTransportReuse(t *testing.T) { t.Fatal(err) } - if httpClient2 == nil { //nolint:staticcheck + if httpClient2 == nil { t.Fatalf("Expected non nil value for http client") } - transport2 := (httpClient2.Transport).(*http.Transport) //nolint:staticcheck + transport2 := (httpClient2.Transport).(*http.Transport) if transport1 != transport2 { t.Fatalf("Expected default transport to be reused") @@ -641,11 +641,11 @@ func TestHTTPTransportOption(t *testing.T) { t.Fatal(err) } - if httpClient1 == nil { //nolint:staticcheck + if httpClient1 == nil { t.Fatalf("Expected non nil value for http client") } - transport1 := (httpClient1.Transport).(*http.Transport) //nolint:staticcheck + transport1 := (httpClient1.Transport).(*http.Transport) if transport1 != transport { t.Fatalf("Expected transport option to be applied") @@ -657,11 +657,11 @@ func TestHTTPTransportOption(t *testing.T) { t.Fatal(err) } - if httpClient2 == nil { //nolint:staticcheck + if httpClient2 == nil { t.Fatalf("Expected non nil value for http client") } - transport2 := (httpClient2.Transport).(*http.Transport) //nolint:staticcheck + transport2 := (httpClient2.Transport).(*http.Transport) if transport1 != transport2 { t.Fatalf("Expected applied transport to be reused") diff --git a/pkg/provenance/sign.go b/pkg/provenance/sign.go index f878698fb..45d4fe1a5 100644 --- a/pkg/provenance/sign.go +++ b/pkg/provenance/sign.go @@ -25,9 +25,9 @@ import ( "os" "strings" - "github.com/ProtonMail/go-crypto/openpgp" //nolint - "github.com/ProtonMail/go-crypto/openpgp/clearsign" //nolint - "github.com/ProtonMail/go-crypto/openpgp/packet" //nolint + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/clearsign" + "github.com/ProtonMail/go-crypto/openpgp/packet" "sigs.k8s.io/yaml" ) diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go index 1985e9eea..eef01c52e 100644 --- a/pkg/provenance/sign_test.go +++ b/pkg/provenance/sign_test.go @@ -24,8 +24,8 @@ import ( "strings" "testing" - pgperrors "github.com/ProtonMail/go-crypto/openpgp/errors" //nolint - "github.com/ProtonMail/go-crypto/openpgp/packet" //nolint + pgperrors "github.com/ProtonMail/go-crypto/openpgp/errors" + "github.com/ProtonMail/go-crypto/openpgp/packet" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sigs.k8s.io/yaml" diff --git a/pkg/repo/v1/repo_test.go b/pkg/repo/v1/repo_test.go index bdaa61eda..93745be1b 100644 --- a/pkg/repo/v1/repo_test.go +++ b/pkg/repo/v1/repo_test.go @@ -114,11 +114,11 @@ func TestRepoFile_Get(t *testing.T) { name := "second" entry := repo.Get(name) - if entry == nil { //nolint:staticcheck + if entry == nil { t.Fatalf("Expected repo entry %q to be found", name) } - if entry.URL != "https://example.com/second" { //nolint:staticcheck + if entry.URL != "https://example.com/second" { t.Errorf("Expected repo URL to be %q but got %q", "https://example.com/second", entry.URL) } From 36cb3a2fe92a4564d2d7d79141f209af19b45d40 Mon Sep 17 00:00:00 2001 From: Mads Jensen Date: Sun, 22 Feb 2026 20:55:05 +0100 Subject: [PATCH 195/571] Replace unneeded use of t.Fatalf with t.Fatal Signed-off-by: Mads Jensen --- internal/chart/v3/lint/lint_test.go | 2 +- internal/chart/v3/util/chartfile_test.go | 4 ++-- internal/chart/v3/util/dependencies_test.go | 6 +++--- internal/chart/v3/util/save_test.go | 2 +- internal/plugin/installer/vcs_installer_test.go | 8 ++++---- internal/plugin/installer/verification_test.go | 8 ++++---- internal/plugin/subprocess_commands_test.go | 4 ++-- internal/tlsutil/tls_test.go | 12 ++++++------ pkg/action/install_test.go | 2 +- pkg/chart/common/util/jsonschema_test.go | 10 +++++----- pkg/chart/v2/lint/lint_test.go | 2 +- pkg/chart/v2/util/chartfile_test.go | 4 ++-- pkg/chart/v2/util/dependencies_test.go | 6 +++--- pkg/chart/v2/util/save_test.go | 2 +- pkg/cmd/create_test.go | 8 ++++---- pkg/cmd/dependency_update_test.go | 2 +- pkg/getter/httpgetter_test.go | 16 ++++++++-------- pkg/getter/ocigetter_test.go | 6 +++--- pkg/registry/client_test.go | 2 +- pkg/registry/tag_test.go | 4 ++-- pkg/repo/v1/index_test.go | 2 +- pkg/repo/v1/repotest/server.go | 6 +++--- pkg/storage/storage_test.go | 4 ++-- 23 files changed, 61 insertions(+), 61 deletions(-) diff --git a/internal/chart/v3/lint/lint_test.go b/internal/chart/v3/lint/lint_test.go index 221de8572..d8242dc95 100644 --- a/internal/chart/v3/lint/lint_test.go +++ b/internal/chart/v3/lint/lint_test.go @@ -231,7 +231,7 @@ func TestMalformedTemplate(t *testing.T) { }() select { case <-c: - t.Fatalf("lint malformed template timeout") + t.Fatal("lint malformed template timeout") case <-ch: if len(m) != 1 { t.Fatalf("All didn't fail with expected errors, got %#v", m) diff --git a/internal/chart/v3/util/chartfile_test.go b/internal/chart/v3/util/chartfile_test.go index 579313f16..32975d21f 100644 --- a/internal/chart/v3/util/chartfile_test.go +++ b/internal/chart/v3/util/chartfile_test.go @@ -64,7 +64,7 @@ func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { } if len(f.Sources) != 1 { - t.Fatalf("Unexpected number of sources") + t.Fatal("Unexpected number of sources") } if f.Sources[0] != "https://example.com/foo/bar" { @@ -84,7 +84,7 @@ func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { } if len(f.Annotations) != 2 { - t.Fatalf("Unexpected annotations") + t.Fatal("Unexpected annotations") } if want, got := "extravalue", f.Annotations["extrakey"]; want != got { diff --git a/internal/chart/v3/util/dependencies_test.go b/internal/chart/v3/util/dependencies_test.go index 82c6ee8fc..c8a176725 100644 --- a/internal/chart/v3/util/dependencies_test.go +++ b/internal/chart/v3/util/dependencies_test.go @@ -385,7 +385,7 @@ func TestGetAliasDependency(t *testing.T) { req := c.Metadata.Dependencies if len(req) == 0 { - t.Fatalf("there are no dependencies to test") + t.Fatal("there are no dependencies to test") } // Success case @@ -403,7 +403,7 @@ func TestGetAliasDependency(t *testing.T) { if req[0].Version != "" { if !IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version) { - t.Fatalf("dependency chart version is not in the compatible range") + t.Fatal("dependency chart version is not in the compatible range") } } @@ -415,7 +415,7 @@ func TestGetAliasDependency(t *testing.T) { req[0].Version = "something else which is not in the compatible range" if IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version) { - t.Fatalf("dependency chart version which is not in the compatible range should cause a failure other than a success ") + t.Fatal("dependency chart version outside the compatible range should not be considered compatible") } } diff --git a/internal/chart/v3/util/save_test.go b/internal/chart/v3/util/save_test.go index 1950faa22..0655e38d4 100644 --- a/internal/chart/v3/util/save_test.go +++ b/internal/chart/v3/util/save_test.go @@ -87,7 +87,7 @@ func TestSave(t *testing.T) { t.Fatalf("Schema data did not match.\nExpected:\n%s\nActual:\n%s", formattedExpected, formattedActual) } if _, err := Save(&chartWithInvalidJSON, dest); err == nil { - t.Fatalf("Invalid JSON was not caught while saving chart") + t.Fatal("Invalid JSON was not caught while saving chart") } c.Metadata.APIVersion = chart.APIVersionV3 diff --git a/internal/plugin/installer/vcs_installer_test.go b/internal/plugin/installer/vcs_installer_test.go index d542a0f75..7fe627b59 100644 --- a/internal/plugin/installer/vcs_installer_test.go +++ b/internal/plugin/installer/vcs_installer_test.go @@ -90,14 +90,14 @@ func TestVCSInstaller(t *testing.T) { // Install again to test plugin exists error if err := Install(i); err == nil { - t.Fatalf("expected error for plugin exists, got none") + t.Fatal("expected error for plugin exists, got none") } else if err.Error() != "plugin already exists" { t.Fatalf("expected error for plugin exists, got (%v)", err) } // Testing FindSource method, expect error because plugin code is not a cloned repository if _, err := FindSource(i.Path()); err == nil { - t.Fatalf("expected error for inability to find plugin source, got none") + t.Fatal("expected error for inability to find plugin source, got none") } else if err.Error() != "cannot get information about plugin source" { t.Fatalf("expected error for inability to find plugin source, got (%v)", err) } @@ -120,7 +120,7 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) { } if err := Install(i); err == nil { - t.Fatalf("expected error for version does not exists, got none") + t.Fatal("expected error for version does not exists, got none") } else if strings.Contains(err.Error(), "Could not resolve host: github.com") { t.Skip("Unable to run test without Internet access") } else if err.Error() != fmt.Sprintf("requested version %q does not exist for plugin %q", version, source) { @@ -181,7 +181,7 @@ func TestVCSInstallerUpdate(t *testing.T) { } // Testing update for error if err := Update(vcsInstaller); err == nil { - t.Fatalf("expected error for plugin modified, got none") + t.Fatal("expected error for plugin modified, got none") } else if err.Error() != "plugin repo was modified" { t.Fatalf("expected error for plugin modified, got (%v)", err) } diff --git a/internal/plugin/installer/verification_test.go b/internal/plugin/installer/verification_test.go index 22f0a8308..4227c8058 100644 --- a/internal/plugin/installer/verification_test.go +++ b/internal/plugin/installer/verification_test.go @@ -107,7 +107,7 @@ func TestInstallWithOptions_VerifyWithValidProvenance(t *testing.T) { // Should fail due to invalid signature (empty keyring) but we test that it gets past the hash check if err == nil { - t.Fatalf("Expected installation to fail with empty keyring") + t.Fatal("Expected installation to fail with empty keyring") } if !strings.Contains(err.Error(), "plugin verification failed") { t.Errorf("Expected plugin verification failed error, got: %v", err) @@ -218,7 +218,7 @@ func TestInstallWithOptions_VerifyDirectoryNotSupported(t *testing.T) { // Should fail with verification not supported error if err == nil { - t.Fatalf("Expected installation to fail with verification not supported error") + t.Fatal("Expected installation to fail with verification not supported error") } if !strings.Contains(err.Error(), "--verify is only supported for plugin tarballs") { t.Errorf("Expected verification not supported error, got: %v", err) @@ -257,7 +257,7 @@ func TestInstallWithOptions_VerifyMismatchedProvenance(t *testing.T) { // Should fail with verification error if err == nil { - t.Fatalf("Expected installation to fail with hash mismatch") + t.Fatal("Expected installation to fail with hash mismatch") } if !strings.Contains(err.Error(), "plugin verification failed") { t.Errorf("Expected plugin verification failed error, got: %v", err) @@ -298,7 +298,7 @@ func TestInstallWithOptions_VerifyProvenanceAccessError(t *testing.T) { // Should fail with access error (either at stat level or during verification) if err == nil { - t.Fatalf("Expected installation to fail with provenance file access error") + t.Fatal("Expected installation to fail with provenance file access error") } // The error could be either "failed to access provenance file" or "plugin verification failed" // depending on when the permission error occurs diff --git a/internal/plugin/subprocess_commands_test.go b/internal/plugin/subprocess_commands_test.go index 8e9c1663e..df854b4ca 100644 --- a/internal/plugin/subprocess_commands_test.go +++ b/internal/plugin/subprocess_commands_test.go @@ -210,7 +210,7 @@ func TestPrepareCommandsNoMatch(t *testing.T) { env := map[string]string{} if _, _, err := PrepareCommands(cmds, true, []string{}, env); err == nil { - t.Fatalf("Expected error to be returned") + t.Fatal("Expected error to be returned") } } @@ -219,7 +219,7 @@ func TestPrepareCommandsNoCommands(t *testing.T) { env := map[string]string{} if _, _, err := PrepareCommands(cmds, true, []string{}, env); err == nil { - t.Fatalf("Expected error to be returned") + t.Fatal("Expected error to be returned") } } diff --git a/internal/tlsutil/tls_test.go b/internal/tlsutil/tls_test.go index f16eb218f..e6859f99f 100644 --- a/internal/tlsutil/tls_test.go +++ b/internal/tlsutil/tls_test.go @@ -58,10 +58,10 @@ func TestNewTLSConfig(t *testing.T) { t.Fatalf("expecting 1 client certificates, got %d", got) } if cfg.InsecureSkipVerify { - t.Fatalf("insecure skip verify mismatch, expecting false") + t.Fatal("insecure skip verify mismatch, expecting false") } if cfg.RootCAs == nil { - t.Fatalf("mismatch tls RootCAs, expecting non-nil") + t.Fatal("mismatch tls RootCAs, expecting non-nil") } } { @@ -77,10 +77,10 @@ func TestNewTLSConfig(t *testing.T) { t.Fatalf("expecting 0 client certificates, got %d", got) } if cfg.InsecureSkipVerify { - t.Fatalf("insecure skip verify mismatch, expecting false") + t.Fatal("insecure skip verify mismatch, expecting false") } if cfg.RootCAs == nil { - t.Fatalf("mismatch tls RootCAs, expecting non-nil") + t.Fatal("mismatch tls RootCAs, expecting non-nil") } } @@ -97,10 +97,10 @@ func TestNewTLSConfig(t *testing.T) { t.Fatalf("expecting 1 client certificates, got %d", got) } if cfg.InsecureSkipVerify { - t.Fatalf("insecure skip verify mismatch, expecting false") + t.Fatal("insecure skip verify mismatch, expecting false") } if cfg.RootCAs != nil { - t.Fatalf("mismatch tls RootCAs, expecting nil") + t.Fatal("mismatch tls RootCAs, expecting nil") } } } diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index a664a3473..5b92258e0 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -505,7 +505,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { vals = map[string]any{} _, err = instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) if err == nil { - t.Fatalf("Did not get expected an error when dry-run false and hide secret is true") + t.Fatal("Did not get the expected error when dry-run is false and hide secret is true") } } diff --git a/pkg/chart/common/util/jsonschema_test.go b/pkg/chart/common/util/jsonschema_test.go index 24073175c..838d152a1 100644 --- a/pkg/chart/common/util/jsonschema_test.go +++ b/pkg/chart/common/util/jsonschema_test.go @@ -54,7 +54,7 @@ func TestValidateAgainstInvalidSingleSchema(t *testing.T) { var errString string if err := ValidateAgainstSingleSchema(values, schema); err == nil { - t.Fatalf("Expected an error, but got nil") + t.Fatal("Expected an error, but got nil") } else { errString = err.Error() } @@ -78,7 +78,7 @@ func TestValidateAgainstSingleSchemaNegative(t *testing.T) { var errString string if err := ValidateAgainstSingleSchema(values, schema); err == nil { - t.Fatalf("Expected an error, but got nil") + t.Fatal("Expected an error, but got nil") } else { errString = err.Error() } @@ -172,7 +172,7 @@ func TestValidateAgainstSchemaNegative(t *testing.T) { var errString string if err := ValidateAgainstSchema(chrt, vals); err == nil { - t.Fatalf("Expected an error, but got nil") + t.Fatal("Expected an error, but got nil") } else { errString = err.Error() } @@ -236,7 +236,7 @@ func TestValidateAgainstSchema2020Negative(t *testing.T) { var errString string if err := ValidateAgainstSchema(chrt, vals); err == nil { - t.Fatalf("Expected an error, but got nil") + t.Fatal("Expected an error, but got nil") } else { errString = err.Error() } @@ -386,6 +386,6 @@ func TestValidateAgainstSchema_InvalidSubchartValuesType_NoPanic(t *testing.T) { // We expect a non-nil error (invalid type), but crucially no panic. if err := ValidateAgainstSchema(chrt, vals); err == nil { - t.Fatalf("expected an error when subchart values have invalid type, got nil") + t.Fatal("expected an error when subchart values have invalid type, got nil") } } diff --git a/pkg/chart/v2/lint/lint_test.go b/pkg/chart/v2/lint/lint_test.go index 80dcef932..b6a5af65e 100644 --- a/pkg/chart/v2/lint/lint_test.go +++ b/pkg/chart/v2/lint/lint_test.go @@ -235,7 +235,7 @@ func TestMalformedTemplate(t *testing.T) { }() select { case <-c: - t.Fatalf("lint malformed template timeout") + t.Fatal("lint malformed template timeout") case <-ch: if len(m) != 1 { t.Fatalf("All didn't fail with expected errors, got %#v", m) diff --git a/pkg/chart/v2/util/chartfile_test.go b/pkg/chart/v2/util/chartfile_test.go index c360b523e..ebadc3414 100644 --- a/pkg/chart/v2/util/chartfile_test.go +++ b/pkg/chart/v2/util/chartfile_test.go @@ -68,7 +68,7 @@ func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { } if len(f.Sources) != 1 { - t.Fatalf("Unexpected number of sources") + t.Fatal("Unexpected number of sources") } if f.Sources[0] != "https://example.com/foo/bar" { @@ -88,7 +88,7 @@ func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { } if len(f.Annotations) != 2 { - t.Fatalf("Unexpected annotations") + t.Fatal("Unexpected annotations") } if want, got := "extravalue", f.Annotations["extrakey"]; want != got { diff --git a/pkg/chart/v2/util/dependencies_test.go b/pkg/chart/v2/util/dependencies_test.go index d9619e76f..0e4df8528 100644 --- a/pkg/chart/v2/util/dependencies_test.go +++ b/pkg/chart/v2/util/dependencies_test.go @@ -385,7 +385,7 @@ func TestGetAliasDependency(t *testing.T) { req := c.Metadata.Dependencies if len(req) == 0 { - t.Fatalf("there are no dependencies to test") + t.Fatal("there are no dependencies to test") } // Success case @@ -403,7 +403,7 @@ func TestGetAliasDependency(t *testing.T) { if req[0].Version != "" { if !IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version) { - t.Fatalf("dependency chart version is not in the compatible range") + t.Fatal("dependency chart version is not in the compatible range") } } @@ -415,7 +415,7 @@ func TestGetAliasDependency(t *testing.T) { req[0].Version = "something else which is not in the compatible range" if IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version) { - t.Fatalf("dependency chart version which is not in the compatible range should cause a failure other than a success ") + t.Fatal("dependency chart version outside the compatible range should fail, but it succeeded") } } diff --git a/pkg/chart/v2/util/save_test.go b/pkg/chart/v2/util/save_test.go index f8e137d0c..c49094a5c 100644 --- a/pkg/chart/v2/util/save_test.go +++ b/pkg/chart/v2/util/save_test.go @@ -90,7 +90,7 @@ func TestSave(t *testing.T) { t.Fatalf("Schema data did not match.\nExpected:\n%s\nActual:\n%s", formattedExpected, formattedActual) } if _, err := Save(&chartWithInvalidJSON, dest); err == nil { - t.Fatalf("Invalid JSON was not caught while saving chart") + t.Fatal("Invalid JSON was not caught while saving chart") } c.Metadata.APIVersion = chart.APIVersionV2 diff --git a/pkg/cmd/create_test.go b/pkg/cmd/create_test.go index 6e95094c7..ee025b778 100644 --- a/pkg/cmd/create_test.go +++ b/pkg/cmd/create_test.go @@ -48,7 +48,7 @@ func TestCreateCmd(t *testing.T) { if fi, err := os.Stat(cname); err != nil { t.Fatalf("no chart directory: %s", err) } else if !fi.IsDir() { - t.Fatalf("chart is not directory") + t.Fatal("chart is not directory") } c, err := chartloader.LoadDir(cname) @@ -156,7 +156,7 @@ func TestCreateStarterCmd(t *testing.T) { if fi, err := os.Stat(cname); err != nil { t.Fatalf("no chart directory: %s", err) } else if !fi.IsDir() { - t.Fatalf("chart is not directory") + t.Fatal("chart is not directory") } // Load and verify the chart @@ -216,7 +216,7 @@ func TestCreateCmdChartAPIVersionV2(t *testing.T) { if fi, err := os.Stat(cname); err != nil { t.Fatalf("no chart directory: %s", err) } else if !fi.IsDir() { - t.Fatalf("chart is not directory") + t.Fatal("chart is not directory") } c, err := chartloader.LoadDir(cname) @@ -257,7 +257,7 @@ func TestCreateCmdChartAPIVersionV3(t *testing.T) { if fi, err := os.Stat(cname); err != nil { t.Fatalf("no chart directory: %s", err) } else if !fi.IsDir() { - t.Fatalf("chart is not directory") + t.Fatal("chart is not directory") } c, err := chartloader.LoadDir(cname) diff --git a/pkg/cmd/dependency_update_test.go b/pkg/cmd/dependency_update_test.go index 3eaa51df1..f9ff68097 100644 --- a/pkg/cmd/dependency_update_test.go +++ b/pkg/cmd/dependency_update_test.go @@ -208,7 +208,7 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) { // Make sure tmpcharts-x is deleted tmpPath := filepath.Join(dir(chartname), fmt.Sprintf("tmpcharts-%d", os.Getpid())) if _, err := os.Stat(tmpPath); !errors.Is(err, fs.ErrNotExist) { - t.Fatalf("tmpcharts dir still exists") + t.Fatal("tmpcharts dir still exists") } } diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index e7c3f3cb1..715aed01e 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -584,7 +584,7 @@ func verifyInsecureSkipVerify(t *testing.T, g *HTTPGetter, caseName string, expe } if returnVal == nil { - t.Fatalf("Expected non nil value for http client") + t.Fatal("Expected non nil value for http client") } transport := (returnVal.Transport).(*http.Transport) gotValue := false @@ -608,7 +608,7 @@ func TestDefaultHTTPTransportReuse(t *testing.T) { } if httpClient1 == nil { - t.Fatalf("Expected non nil value for http client") + t.Fatal("Expected non nil value for http client") } transport1 := (httpClient1.Transport).(*http.Transport) @@ -620,13 +620,13 @@ func TestDefaultHTTPTransportReuse(t *testing.T) { } if httpClient2 == nil { - t.Fatalf("Expected non nil value for http client") + t.Fatal("Expected non nil value for http client") } transport2 := (httpClient2.Transport).(*http.Transport) if transport1 != transport2 { - t.Fatalf("Expected default transport to be reused") + t.Fatal("Expected default transport to be reused") } } @@ -642,13 +642,13 @@ func TestHTTPTransportOption(t *testing.T) { } if httpClient1 == nil { - t.Fatalf("Expected non nil value for http client") + t.Fatal("Expected non nil value for http client") } transport1 := (httpClient1.Transport).(*http.Transport) if transport1 != transport { - t.Fatalf("Expected transport option to be applied") + t.Fatal("Expected transport option to be applied") } httpClient2, err := g.httpClient(g.opts) @@ -658,13 +658,13 @@ func TestHTTPTransportOption(t *testing.T) { } if httpClient2 == nil { - t.Fatalf("Expected non nil value for http client") + t.Fatal("Expected non nil value for http client") } transport2 := (httpClient2.Transport).(*http.Transport) if transport1 != transport2 { - t.Fatalf("Expected applied transport to be reused") + t.Fatal("Expected applied transport to be reused") } g = HTTPGetter{} diff --git a/pkg/getter/ocigetter_test.go b/pkg/getter/ocigetter_test.go index ef196afcc..5a7c99ebe 100644 --- a/pkg/getter/ocigetter_test.go +++ b/pkg/getter/ocigetter_test.go @@ -128,7 +128,7 @@ func TestOCIHTTPTransportReuse(t *testing.T) { } if g.transport == nil { - t.Fatalf("Expected non nil value for transport") + t.Fatal("Expected non nil value for transport") } transport1 := g.transport @@ -140,12 +140,12 @@ func TestOCIHTTPTransportReuse(t *testing.T) { } if g.transport == nil { - t.Fatalf("Expected non nil value for transport") + t.Fatal("Expected non nil value for transport") } transport2 := g.transport if transport1 != transport2 { - t.Fatalf("Expected default transport to be reused") + t.Fatal("Expected default transport to be reused") } } diff --git a/pkg/registry/client_test.go b/pkg/registry/client_test.go index 98a8b2ea3..74711f6c3 100644 --- a/pkg/registry/client_test.go +++ b/pkg/registry/client_test.go @@ -82,7 +82,7 @@ func TestLogin_ResetsForceAttemptOAuth2_OnSuccess(t *testing.T) { } if c.authorizer == nil || c.authorizer.ForceAttemptOAuth2 { - t.Fatalf("expected ForceAttemptOAuth2 default to be false") + t.Fatal("expected ForceAttemptOAuth2 default to be false") } // Call Login with plain HTTP against our test server diff --git a/pkg/registry/tag_test.go b/pkg/registry/tag_test.go index 09f0f12ea..e3ec47703 100644 --- a/pkg/registry/tag_test.go +++ b/pkg/registry/tag_test.go @@ -71,7 +71,7 @@ func TestGetTagMatchingVersionOrConstraint_InvalidConstraint(t *testing.T) { tags := []string{"1.0.0"} _, err := GetTagMatchingVersionOrConstraint(tags, ">a1") if err == nil { - t.Fatalf("expected error for invalid constraint") + t.Fatal("expected error for invalid constraint") } } @@ -79,7 +79,7 @@ func TestGetTagMatchingVersionOrConstraint_NoMatches(t *testing.T) { tags := []string{"0.1.0", "0.2.0"} _, err := GetTagMatchingVersionOrConstraint(tags, ">=1.0.0") if err == nil { - t.Fatalf("expected error when no tags match") + t.Fatal("expected error when no tags match") } if !strings.Contains(err.Error(), ">=1.0.0") { t.Fatalf("expected error to contain version string, got: %v", err) diff --git a/pkg/repo/v1/index_test.go b/pkg/repo/v1/index_test.go index 446160143..37cd6e18a 100644 --- a/pkg/repo/v1/index_test.go +++ b/pkg/repo/v1/index_test.go @@ -370,7 +370,7 @@ func verifyLocalIndex(t *testing.T, i *IndexFile) { nginx, ok := i.Entries["nginx"] if !ok || len(nginx) != 2 { - t.Fatalf("Expected 2 nginx entries") + t.Fatal("Expected 2 nginx entries") } expects := []*ChartVersion{ diff --git a/pkg/repo/v1/repotest/server.go b/pkg/repo/v1/repotest/server.go index bb8c2e0bf..6a4580221 100644 --- a/pkg/repo/v1/repotest/server.go +++ b/pkg/repo/v1/repotest/server.go @@ -175,14 +175,14 @@ func NewOCIServer(t *testing.T, dir string) (*OCIServer, error) { htpasswdPath := filepath.Join(dir, testHtpasswdFileBasename) err = os.WriteFile(htpasswdPath, fmt.Appendf(nil, "%s:%s\n", testUsername, string(pwBytes)), 0o644) if err != nil { - t.Fatalf("error creating test htpasswd file") + t.Fatal("error creating test htpasswd file") } // Registry config config := &configuration.Configuration{} ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { - t.Fatalf("error finding free port for test registry") + t.Fatalf("error finding free port for test registry: %v", err) } defer ln.Close() @@ -237,7 +237,7 @@ func (srv *OCIServer) RunWithReturn(t *testing.T, opts ...OCIServerOpt) *OCIServ ociRegistry.ClientOptCredentialsFile(credentialsFile), ) if err != nil { - t.Fatalf("error creating registry client") + t.Fatalf("error creating registry client: %v", err) } err = registryClient.Login( diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index 572342a20..0ae5321ba 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -208,7 +208,7 @@ func TestStorageDeployed(t *testing.T) { switch { case rls == nil: - t.Fatalf("Release is nil") + t.Fatal("Release is nil") case rel.Name != name: t.Fatalf("Expected release name %q, actual %q\n", name, rel.Name) case rel.Version != vers: @@ -251,7 +251,7 @@ func TestStorageDeployedWithCorruption(t *testing.T) { switch { case rls == nil: - t.Fatalf("Release is nil") + t.Fatal("Release is nil") case rel.Name != name: t.Fatalf("Expected release name %q, actual %q\n", name, rel.Name) case rel.Version != vers: From 9409226e15b26d05621f0b263f4ad6f597dfb7aa Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 26 Feb 2026 18:55:55 +0100 Subject: [PATCH 196/571] chore(internal): enable perfsprint linter #### Description enable perfsprint linter in internal/plugin/installer Signed-off-by: Matthieu MOREL --- internal/chart/v3/lint/rules/values.go | 3 ++- internal/chart/v3/util/save_test.go | 4 ++-- internal/plugin/installer/http_installer_test.go | 2 +- internal/plugin/installer/local_installer.go | 2 +- internal/plugin/installer/oci_installer.go | 2 +- internal/plugin/installer/oci_installer_test.go | 2 +- internal/plugin/loader_test.go | 3 +-- internal/plugin/runtime_subprocess_getter.go | 3 ++- internal/plugin/schema/getter.go | 3 ++- internal/resolver/resolver.go | 2 +- 10 files changed, 14 insertions(+), 12 deletions(-) diff --git a/internal/chart/v3/lint/rules/values.go b/internal/chart/v3/lint/rules/values.go index ba371cbe2..b4a2edb0c 100644 --- a/internal/chart/v3/lint/rules/values.go +++ b/internal/chart/v3/lint/rules/values.go @@ -17,6 +17,7 @@ limitations under the License. package rules import ( + "errors" "fmt" "os" "path/filepath" @@ -47,7 +48,7 @@ func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]any, func validateValuesFileExistence(valuesPath string) error { _, err := os.Stat(valuesPath) if err != nil { - return fmt.Errorf("file does not exist") + return errors.New("file does not exist") } return nil } diff --git a/internal/chart/v3/util/save_test.go b/internal/chart/v3/util/save_test.go index 1950faa22..73a8dadd1 100644 --- a/internal/chart/v3/util/save_test.go +++ b/internal/chart/v3/util/save_test.go @@ -21,8 +21,8 @@ import ( "bytes" "compress/gzip" "crypto/sha256" + "encoding/hex" "errors" - "fmt" "io" "os" "path" @@ -353,5 +353,5 @@ func sha256Sum(filePath string) (string, error) { return "", err } - return fmt.Sprintf("%x", h.Sum(nil)), nil + return hex.EncodeToString(h.Sum(nil)), nil } diff --git a/internal/plugin/installer/http_installer_test.go b/internal/plugin/installer/http_installer_test.go index 7f7e6cef6..85a84ee31 100644 --- a/internal/plugin/installer/http_installer_test.go +++ b/internal/plugin/installer/http_installer_test.go @@ -150,7 +150,7 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) { // inject fake http client responding with error httpInstaller.getter = &TestHTTPGetter{ - MockError: fmt.Errorf("failed to download plugin for some reason"), + MockError: errors.New("failed to download plugin for some reason"), } // attempt to install the plugin diff --git a/internal/plugin/installer/local_installer.go b/internal/plugin/installer/local_installer.go index 1c8314282..71407380f 100644 --- a/internal/plugin/installer/local_installer.go +++ b/internal/plugin/installer/local_installer.go @@ -188,7 +188,7 @@ func (i *LocalInstaller) SupportsVerification() bool { // GetVerificationData loads plugin and provenance data from local files for verification func (i *LocalInstaller) GetVerificationData() (archiveData, provData []byte, filename string, err error) { if !i.SupportsVerification() { - return nil, nil, "", fmt.Errorf("verification not supported for directories") + return nil, nil, "", errors.New("verification not supported for directories") } // Read and cache the plugin archive file diff --git a/internal/plugin/installer/oci_installer.go b/internal/plugin/installer/oci_installer.go index 67f99b6f8..50d01522a 100644 --- a/internal/plugin/installer/oci_installer.go +++ b/internal/plugin/installer/oci_installer.go @@ -130,7 +130,7 @@ func (i *OCIInstaller) Install() error { // Check if this is a gzip compressed file if len(i.pluginData) < 2 || i.pluginData[0] != 0x1f || i.pluginData[1] != 0x8b { - return fmt.Errorf("plugin data is not a gzip compressed archive") + return errors.New("plugin data is not a gzip compressed archive") } // Create cache directory diff --git a/internal/plugin/installer/oci_installer_test.go b/internal/plugin/installer/oci_installer_test.go index 1280cf97d..4576f1c71 100644 --- a/internal/plugin/installer/oci_installer_test.go +++ b/internal/plugin/installer/oci_installer_test.go @@ -82,7 +82,7 @@ command: "$HELM_PLUGIN_DIR/bin/%s" // Add executable execContent := fmt.Sprintf("#!/bin/sh\necho '%s test plugin'", pluginName) execHeader := &tar.Header{ - Name: fmt.Sprintf("bin/%s", pluginName), + Name: "bin/" + pluginName, Mode: 0755, Size: int64(len(execContent)), Typeflag: tar.TypeReg, diff --git a/internal/plugin/loader_test.go b/internal/plugin/loader_test.go index e84905248..03ef02c85 100644 --- a/internal/plugin/loader_test.go +++ b/internal/plugin/loader_test.go @@ -17,7 +17,6 @@ package plugin import ( "bytes" - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -71,7 +70,7 @@ func TestLoadDir(t *testing.T) { } return Metadata{ APIVersion: apiVersion, - Name: fmt.Sprintf("hello-%s", apiVersion), + Name: "hello-" + apiVersion, Version: "0.1.0", Type: "cli/v1", Runtime: "subprocess", diff --git a/internal/plugin/runtime_subprocess_getter.go b/internal/plugin/runtime_subprocess_getter.go index fa6f470a9..c7262e0dd 100644 --- a/internal/plugin/runtime_subprocess_getter.go +++ b/internal/plugin/runtime_subprocess_getter.go @@ -24,6 +24,7 @@ import ( "os/exec" "path/filepath" "slices" + "strconv" "helm.sh/helm/v4/internal/plugin/schema" ) @@ -63,7 +64,7 @@ func (r *SubprocessPluginRuntime) runGetter(input *Input) (*Output, error) { env["HELM_PLUGIN_DIR"] = r.pluginDir env["HELM_PLUGIN_USERNAME"] = msg.Options.Username env["HELM_PLUGIN_PASSWORD"] = msg.Options.Password - env["HELM_PLUGIN_PASS_CREDENTIALS_ALL"] = fmt.Sprintf("%t", msg.Options.PassCredentialsAll) + env["HELM_PLUGIN_PASS_CREDENTIALS_ALL"] = strconv.FormatBool(msg.Options.PassCredentialsAll) command, args, err := PrepareCommands(d.PlatformCommand, false, []string{}, env) if err != nil { diff --git a/internal/plugin/schema/getter.go b/internal/plugin/schema/getter.go index 2c5e81df1..f53ae29bf 100644 --- a/internal/plugin/schema/getter.go +++ b/internal/plugin/schema/getter.go @@ -14,6 +14,7 @@ package schema import ( + "errors" "fmt" "time" ) @@ -55,7 +56,7 @@ type ConfigGetterV1 struct { func (c *ConfigGetterV1) Validate() error { if len(c.Protocols) == 0 { - return fmt.Errorf("getter has no protocols") + return errors.New("getter has no protocols") } for i, protocol := range c.Protocols { if protocol == "" { diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index 3efe94f10..184c8404b 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -149,7 +149,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string } else { // Retrieve list of tags for repository - ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, fmt.Sprintf("%s://", registry.OCIScheme)), d.Name) + ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, registry.OCIScheme+"://"), d.Name) tags, err := r.registryClient.Tags(ref) if err != nil { return nil, fmt.Errorf("could not retrieve list of tags for repository %s: %w", d.Repository, err) From 6c2cb2f54784b5ef6158dca0660f428a3baf75f5 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 26 Feb 2026 18:55:55 +0100 Subject: [PATCH 197/571] chore(pkg): enable perfsprint linter #### Description enable perfsprint linter in pkg/cmd Signed-off-by: Matthieu MOREL --- pkg/action/install_test.go | 14 +++++++------- pkg/action/upgrade_test.go | 12 ++++++------ pkg/cmd/install_test.go | 12 ++++++------ pkg/cmd/lint_test.go | 14 +++++++------- pkg/cmd/pull_test.go | 12 ++++++------ pkg/cmd/show_test.go | 16 ++++++++-------- pkg/cmd/template_test.go | 10 +++++----- pkg/cmd/upgrade_test.go | 16 ++++++++-------- pkg/kube/client.go | 10 +++++----- pkg/kube/statuswait_test.go | 14 +++++++------- 10 files changed, 65 insertions(+), 65 deletions(-) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index a664a3473..63d59ddc9 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -384,7 +384,7 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) { rel, err := releaserToV1Release(r) is.NoError(err) - expectedNotes := fmt.Sprintf("got-%s", res.Name) + expectedNotes := "got-" + res.Name is.Equal(expectedNotes, rel.Info.Notes) is.Equal(rel.Info.Description, "Install complete") } @@ -571,7 +571,7 @@ func TestInstallRelease_FailedHooks(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "failed-hooks" failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WatchUntilReadyError = fmt.Errorf("Failed watch") + failer.WatchUntilReadyError = errors.New("Failed watch") instAction.cfg.KubeClient = failer outBuffer := &bytes.Buffer{} failer.PrintingKubeClient = kubefake.PrintingKubeClient{Out: io.Discard, LogOutput: outBuffer} @@ -633,7 +633,7 @@ func TestInstallRelease_Wait(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "come-fail-away" failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") + failer.WaitError = errors.New("I timed out") instAction.cfg.KubeClient = failer instAction.WaitStrategy = kube.StatusWatcherStrategy vals := map[string]any{} @@ -677,7 +677,7 @@ func TestInstallRelease_WaitForJobs(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "come-fail-away" failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") + failer.WaitError = errors.New("I timed out") instAction.cfg.KubeClient = failer instAction.WaitStrategy = kube.StatusWatcherStrategy instAction.WaitForJobs = true @@ -698,7 +698,7 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "come-fail-away" failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") + failer.WaitError = errors.New("I timed out") instAction.cfg.KubeClient = failer instAction.RollbackOnFailure = true // disabling hooks to avoid an early fail when @@ -723,8 +723,8 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "come-fail-away-with-me" failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - failer.DeleteError = fmt.Errorf("uninstall fail") + failer.WaitError = errors.New("I timed out") + failer.DeleteError = errors.New("uninstall fail") instAction.cfg.KubeClient = failer instAction.RollbackOnFailure = true vals := map[string]any{} diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index f4606a3e9..393692976 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -91,7 +91,7 @@ func TestUpgradeRelease_Wait(t *testing.T) { require.NoError(t, upAction.cfg.Releases.Create(rel)) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") + failer.WaitError = errors.New("I timed out") upAction.cfg.KubeClient = failer upAction.WaitStrategy = kube.StatusWatcherStrategy vals := map[string]any{} @@ -115,7 +115,7 @@ func TestUpgradeRelease_WaitForJobs(t *testing.T) { require.NoError(t, upAction.cfg.Releases.Create(rel)) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") + failer.WaitError = errors.New("I timed out") upAction.cfg.KubeClient = failer upAction.WaitStrategy = kube.StatusWatcherStrategy upAction.WaitForJobs = true @@ -140,8 +140,8 @@ func TestUpgradeRelease_CleanupOnFail(t *testing.T) { require.NoError(t, upAction.cfg.Releases.Create(rel)) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - failer.DeleteError = fmt.Errorf("I tried to delete nil") + failer.WaitError = errors.New("I timed out") + failer.DeleteError = errors.New("I tried to delete nil") upAction.cfg.KubeClient = failer upAction.WaitStrategy = kube.StatusWatcherStrategy upAction.CleanupOnFail = true @@ -170,7 +170,7 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) { failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) // We can't make Update error because then the rollback won't work - failer.WatchUntilReadyError = fmt.Errorf("arming key removed") + failer.WatchUntilReadyError = errors.New("arming key removed") upAction.cfg.KubeClient = failer upAction.RollbackOnFailure = true vals := map[string]any{} @@ -199,7 +199,7 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) { require.NoError(t, upAction.cfg.Releases.Create(rel)) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.UpdateError = fmt.Errorf("update fail") + failer.UpdateError = errors.New("update fail") upAction.cfg.KubeClient = failer upAction.RollbackOnFailure = true vals := map[string]any{} diff --git a/pkg/cmd/install_test.go b/pkg/cmd/install_test.go index 5fa3c1340..8d3435e03 100644 --- a/pkg/cmd/install_test.go +++ b/pkg/cmd/install_test.go @@ -291,27 +291,27 @@ func TestInstallVersionCompletion(t *testing.T) { tests := []cmdTestCase{{ name: "completion for install version flag with release name", - cmd: fmt.Sprintf("%s __complete install releasename testing/alpine --version ''", repoSetup), + cmd: repoSetup + " __complete install releasename testing/alpine --version ''", golden: "output/version-comp.txt", }, { name: "completion for install version flag with generate-name", - cmd: fmt.Sprintf("%s __complete install --generate-name testing/alpine --version ''", repoSetup), + cmd: repoSetup + " __complete install --generate-name testing/alpine --version ''", golden: "output/version-comp.txt", }, { name: "completion for install version flag, no filter", - cmd: fmt.Sprintf("%s __complete install releasename testing/alpine --version 0.3", repoSetup), + cmd: repoSetup + " __complete install releasename testing/alpine --version 0.3", golden: "output/version-comp.txt", }, { name: "completion for install version flag too few args", - cmd: fmt.Sprintf("%s __complete install testing/alpine --version ''", repoSetup), + cmd: repoSetup + " __complete install testing/alpine --version ''", golden: "output/version-invalid-comp.txt", }, { name: "completion for install version flag too many args", - cmd: fmt.Sprintf("%s __complete install releasename testing/alpine badarg --version ''", repoSetup), + cmd: repoSetup + " __complete install releasename testing/alpine badarg --version ''", golden: "output/version-invalid-comp.txt", }, { name: "completion for install version flag invalid chart", - cmd: fmt.Sprintf("%s __complete install releasename invalid/invalid --version ''", repoSetup), + cmd: repoSetup + " __complete install releasename invalid/invalid --version ''", golden: "output/version-invalid-comp.txt", }} runTestCmd(t, tests) diff --git a/pkg/cmd/lint_test.go b/pkg/cmd/lint_test.go index f825e36e2..a13ec423b 100644 --- a/pkg/cmd/lint_test.go +++ b/pkg/cmd/lint_test.go @@ -25,12 +25,12 @@ func TestLintCmdWithSubchartsFlag(t *testing.T) { testChart := "testdata/testcharts/chart-with-bad-subcharts" tests := []cmdTestCase{{ name: "lint good chart with bad subcharts", - cmd: fmt.Sprintf("lint %s", testChart), + cmd: "lint " + testChart, golden: "output/lint-chart-with-bad-subcharts.txt", wantError: true, }, { name: "lint good chart with bad subcharts using --with-subcharts flag", - cmd: fmt.Sprintf("lint --with-subcharts %s", testChart), + cmd: "lint --with-subcharts " + testChart, golden: "output/lint-chart-with-bad-subcharts-with-subcharts.txt", wantError: true, }} @@ -42,7 +42,7 @@ func TestLintCmdWithQuietFlag(t *testing.T) { testChart2 := "testdata/testcharts/chart-bad-requirements" tests := []cmdTestCase{{ name: "lint good chart using --quiet flag", - cmd: fmt.Sprintf("lint --quiet %s", testChart1), + cmd: "lint --quiet " + testChart1, golden: "output/lint-quiet.txt", }, { name: "lint two charts, one with error using --quiet flag", @@ -67,24 +67,24 @@ func TestLintCmdWithKubeVersionFlag(t *testing.T) { testChart := "testdata/testcharts/chart-with-deprecated-api" tests := []cmdTestCase{{ name: "lint chart with deprecated api version using kube version flag", - cmd: fmt.Sprintf("lint --kube-version 1.22.0 %s", testChart), + cmd: "lint --kube-version 1.22.0 " + testChart, golden: "output/lint-chart-with-deprecated-api.txt", wantError: false, }, { name: "lint chart with deprecated api version using kube version and strict flag", - cmd: fmt.Sprintf("lint --kube-version 1.22.0 --strict %s", testChart), + cmd: "lint --kube-version 1.22.0 --strict " + testChart, golden: "output/lint-chart-with-deprecated-api-strict.txt", wantError: true, }, { // the test builds will use the kubeVersionMinorTesting const in capabilities.go // which is "20" name: "lint chart with deprecated api version without kube version", - cmd: fmt.Sprintf("lint %s", testChart), + cmd: "lint " + testChart, golden: "output/lint-chart-with-deprecated-api-old-k8s.txt", wantError: false, }, { name: "lint chart with deprecated api version with older kube version", - cmd: fmt.Sprintf("lint --kube-version 1.21.0 --strict %s", testChart), + cmd: "lint --kube-version 1.21.0 --strict " + testChart, golden: "output/lint-chart-with-deprecated-api-old-k8s.txt", wantError: false, }} diff --git a/pkg/cmd/pull_test.go b/pkg/cmd/pull_test.go index 6d5eb1482..f749c218c 100644 --- a/pkg/cmd/pull_test.go +++ b/pkg/cmd/pull_test.go @@ -342,7 +342,7 @@ func runPullTests(t *testing.T, tests []struct { func buildOCIURL(registryURL, chartName, version, username, password string) string { baseURL := fmt.Sprintf("oci://%s/u/ocitestuser/%s", registryURL, chartName) if version != "" { - baseURL += fmt.Sprintf(" --version %s", version) + baseURL += " --version " + version } if username != "" && password != "" { baseURL += fmt.Sprintf(" --username %s --password %s", username, password) @@ -416,23 +416,23 @@ func TestPullVersionCompletion(t *testing.T) { tests := []cmdTestCase{{ name: "completion for pull version flag", - cmd: fmt.Sprintf("%s __complete pull testing/alpine --version ''", repoSetup), + cmd: repoSetup + " __complete pull testing/alpine --version ''", golden: "output/version-comp.txt", }, { name: "completion for pull version flag, no filter", - cmd: fmt.Sprintf("%s __complete pull testing/alpine --version 0.3", repoSetup), + cmd: repoSetup + " __complete pull testing/alpine --version 0.3", golden: "output/version-comp.txt", }, { name: "completion for pull version flag too few args", - cmd: fmt.Sprintf("%s __complete pull --version ''", repoSetup), + cmd: repoSetup + " __complete pull --version ''", golden: "output/version-invalid-comp.txt", }, { name: "completion for pull version flag too many args", - cmd: fmt.Sprintf("%s __complete pull testing/alpine badarg --version ''", repoSetup), + cmd: repoSetup + " __complete pull testing/alpine badarg --version ''", golden: "output/version-invalid-comp.txt", }, { name: "completion for pull version flag invalid chart", - cmd: fmt.Sprintf("%s __complete pull invalid/invalid --version ''", repoSetup), + cmd: repoSetup + " __complete pull invalid/invalid --version ''", golden: "output/version-invalid-comp.txt", }} runTestCmd(t, tests) diff --git a/pkg/cmd/show_test.go b/pkg/cmd/show_test.go index ff3671dbc..5da2626d3 100644 --- a/pkg/cmd/show_test.go +++ b/pkg/cmd/show_test.go @@ -99,35 +99,35 @@ func TestShowVersionCompletion(t *testing.T) { tests := []cmdTestCase{{ name: "completion for show version flag", - cmd: fmt.Sprintf("%s __complete show chart testing/alpine --version ''", repoSetup), + cmd: repoSetup + " __complete show chart testing/alpine --version ''", golden: "output/version-comp.txt", }, { name: "completion for show version flag, no filter", - cmd: fmt.Sprintf("%s __complete show chart testing/alpine --version 0.3", repoSetup), + cmd: repoSetup + " __complete show chart testing/alpine --version 0.3", golden: "output/version-comp.txt", }, { name: "completion for show version flag too few args", - cmd: fmt.Sprintf("%s __complete show chart --version ''", repoSetup), + cmd: repoSetup + " __complete show chart --version ''", golden: "output/version-invalid-comp.txt", }, { name: "completion for show version flag too many args", - cmd: fmt.Sprintf("%s __complete show chart testing/alpine badarg --version ''", repoSetup), + cmd: repoSetup + " __complete show chart testing/alpine badarg --version ''", golden: "output/version-invalid-comp.txt", }, { name: "completion for show version flag invalid chart", - cmd: fmt.Sprintf("%s __complete show chart invalid/invalid --version ''", repoSetup), + cmd: repoSetup + " __complete show chart invalid/invalid --version ''", golden: "output/version-invalid-comp.txt", }, { name: "completion for show version flag with all", - cmd: fmt.Sprintf("%s __complete show all testing/alpine --version ''", repoSetup), + cmd: repoSetup + " __complete show all testing/alpine --version ''", golden: "output/version-comp.txt", }, { name: "completion for show version flag with readme", - cmd: fmt.Sprintf("%s __complete show readme testing/alpine --version ''", repoSetup), + cmd: repoSetup + " __complete show readme testing/alpine --version ''", golden: "output/version-comp.txt", }, { name: "completion for show version flag with values", - cmd: fmt.Sprintf("%s __complete show values testing/alpine --version ''", repoSetup), + cmd: repoSetup + " __complete show values testing/alpine --version ''", golden: "output/version-comp.txt", }} runTestCmd(t, tests) diff --git a/pkg/cmd/template_test.go b/pkg/cmd/template_test.go index 5bcccf5d0..7391781f6 100644 --- a/pkg/cmd/template_test.go +++ b/pkg/cmd/template_test.go @@ -178,23 +178,23 @@ func TestTemplateVersionCompletion(t *testing.T) { tests := []cmdTestCase{{ name: "completion for template version flag with release name", - cmd: fmt.Sprintf("%s __complete template releasename testing/alpine --version ''", repoSetup), + cmd: repoSetup + " __complete template releasename testing/alpine --version ''", golden: "output/version-comp.txt", }, { name: "completion for template version flag with generate-name", - cmd: fmt.Sprintf("%s __complete template --generate-name testing/alpine --version ''", repoSetup), + cmd: repoSetup + " __complete template --generate-name testing/alpine --version ''", golden: "output/version-comp.txt", }, { name: "completion for template version flag too few args", - cmd: fmt.Sprintf("%s __complete template testing/alpine --version ''", repoSetup), + cmd: repoSetup + " __complete template testing/alpine --version ''", golden: "output/version-invalid-comp.txt", }, { name: "completion for template version flag too many args", - cmd: fmt.Sprintf("%s __complete template releasename testing/alpine badarg --version ''", repoSetup), + cmd: repoSetup + " __complete template releasename testing/alpine badarg --version ''", golden: "output/version-invalid-comp.txt", }, { name: "completion for template version flag invalid chart", - cmd: fmt.Sprintf("%s __complete template releasename invalid/invalid --version ''", repoSetup), + cmd: repoSetup + " __complete template releasename invalid/invalid --version ''", golden: "output/version-invalid-comp.txt", }} runTestCmd(t, tests) diff --git a/pkg/cmd/upgrade_test.go b/pkg/cmd/upgrade_test.go index 0ae1e3561..f96f6ec0d 100644 --- a/pkg/cmd/upgrade_test.go +++ b/pkg/cmd/upgrade_test.go @@ -149,7 +149,7 @@ func TestUpgradeCmd(t *testing.T) { }, { name: "upgrade a release with missing dependencies", - cmd: fmt.Sprintf("upgrade bonkers-bunny %s", missingDepsPath), + cmd: "upgrade bonkers-bunny " + missingDepsPath, golden: "output/upgrade-with-missing-dependencies.txt", wantError: true, }, @@ -161,7 +161,7 @@ func TestUpgradeCmd(t *testing.T) { }, { name: "upgrade a release with resolving missing dependencies", - cmd: fmt.Sprintf("upgrade --dependency-update funny-bunny %s", presentDepsPath), + cmd: "upgrade --dependency-update funny-bunny " + presentDepsPath, golden: "output/upgrade-with-dependency-update.txt", rels: []*release.Release{relMock("funny-bunny", 2, ch2)}, }, @@ -443,23 +443,23 @@ func TestUpgradeVersionCompletion(t *testing.T) { tests := []cmdTestCase{{ name: "completion for upgrade version flag", - cmd: fmt.Sprintf("%s __complete upgrade releasename testing/alpine --version ''", repoSetup), + cmd: repoSetup + " __complete upgrade releasename testing/alpine --version ''", golden: "output/version-comp.txt", }, { name: "completion for upgrade version flag, no filter", - cmd: fmt.Sprintf("%s __complete upgrade releasename testing/alpine --version 0.3", repoSetup), + cmd: repoSetup + " __complete upgrade releasename testing/alpine --version 0.3", golden: "output/version-comp.txt", }, { name: "completion for upgrade version flag too few args", - cmd: fmt.Sprintf("%s __complete upgrade releasename --version ''", repoSetup), + cmd: repoSetup + " __complete upgrade releasename --version ''", golden: "output/version-invalid-comp.txt", }, { name: "completion for upgrade version flag too many args", - cmd: fmt.Sprintf("%s __complete upgrade releasename testing/alpine badarg --version ''", repoSetup), + cmd: repoSetup + " __complete upgrade releasename testing/alpine badarg --version ''", golden: "output/version-invalid-comp.txt", }, { name: "completion for upgrade version flag invalid chart", - cmd: fmt.Sprintf("%s __complete upgrade releasename invalid/invalid --version ''", repoSetup), + cmd: repoSetup + " __complete upgrade releasename invalid/invalid --version ''", golden: "output/version-invalid-comp.txt", }} runTestCmd(t, tests) @@ -636,7 +636,7 @@ func TestUpgradeInstallServerSideApply(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { store := storageFixture() - releaseName := fmt.Sprintf("ssa-test-%s", tt.expectedApplyMethod) + releaseName := "ssa-test-" + tt.expectedApplyMethod cmd := fmt.Sprintf("upgrade %s --install %s '%s'", releaseName, tt.serverSideFlag, chartPath) _, _, err := executeActionCommandC(store, cmd) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index fc706496d..5efb03cb0 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -277,7 +277,7 @@ type ClientCreateOption func(*clientCreateOptions) error func ClientCreateOptionServerSideApply(serverSideApply, forceConflicts bool) ClientCreateOption { return func(o *clientCreateOptions) error { if !serverSideApply && forceConflicts { - return fmt.Errorf("forceConflicts enabled when serverSideApply disabled") + return errors.New("forceConflicts enabled when serverSideApply disabled") } o.serverSideApply = serverSideApply @@ -727,7 +727,7 @@ func ClientUpdateOptionThreeWayMergeForUnstructured(threeWayMergeForUnstructured func ClientUpdateOptionServerSideApply(serverSideApply, forceConflicts bool) ClientUpdateOption { return func(o *clientUpdateOptions) error { if !serverSideApply && forceConflicts { - return fmt.Errorf("forceConflicts enabled when serverSideApply disabled") + return errors.New("forceConflicts enabled when serverSideApply disabled") } o.serverSideApply = serverSideApply @@ -811,15 +811,15 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate } if updateOptions.threeWayMergeForUnstructured && updateOptions.serverSideApply { - return &Result{}, fmt.Errorf("invalid operation: cannot use three-way merge for unstructured and server-side apply together") + return &Result{}, errors.New("invalid operation: cannot use three-way merge for unstructured and server-side apply together") } if updateOptions.forceConflicts && updateOptions.forceReplace { - return &Result{}, fmt.Errorf("invalid operation: cannot use force conflicts and force replace together") + return &Result{}, errors.New("invalid operation: cannot use force conflicts and force replace together") } if updateOptions.serverSideApply && updateOptions.forceReplace { - return &Result{}, fmt.Errorf("invalid operation: cannot use server-side apply and force replace together") + return &Result{}, errors.New("invalid operation: cannot use server-side apply and force replace together") } createApplyFunc := c.makeCreateApplyFunc( diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index 0bcf766f0..4b4928d4a 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -715,7 +715,7 @@ func setupRestrictedClient(fakeClient *dynamicfake.FakeDynamicClient, allowedNam return true, nil, apierrors.NewForbidden( action.GetResource().GroupResource(), "", - fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources"), + errors.New("user does not have cluster-wide LIST permissions for cluster-scoped resources"), ) } if !config.allowedNamespaces[ns] { @@ -739,7 +739,7 @@ func setupRestrictedClient(fakeClient *dynamicfake.FakeDynamicClient, allowedNam return true, nil, apierrors.NewForbidden( action.GetResource().GroupResource(), "", - fmt.Errorf("user does not have cluster-wide WATCH permissions for cluster-scoped resources"), + errors.New("user does not have cluster-wide WATCH permissions for cluster-scoped resources"), ) } if !config.allowedNamespaces[ns] { @@ -793,7 +793,7 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { name: "error when cluster-scoped resource included", objManifests: []string{podNamespace1Manifest, clusterRoleManifest}, allowedNamespaces: []string{"namespace-1"}, - expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, + expectErrs: []error{errors.New("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, @@ -802,7 +802,7 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { name: "error when deleting cluster-scoped resource", objManifests: []string{podNamespace1Manifest, namespaceManifest}, allowedNamespaces: []string{"namespace-1"}, - expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, + expectErrs: []error{errors.New("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.WaitForDelete(rl, timeout) }, @@ -892,7 +892,7 @@ func TestStatusWaitMixedResources(t *testing.T) { name: "wait fails when cluster-scoped resource included", objManifests: []string{podNamespace1Manifest, clusterRoleManifest}, allowedNamespaces: []string{"namespace-1"}, - expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, + expectErrs: []error{errors.New("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, @@ -901,7 +901,7 @@ func TestStatusWaitMixedResources(t *testing.T) { name: "waitForDelete fails when cluster-scoped resource included", objManifests: []string{podNamespace1Manifest, clusterRoleManifest}, allowedNamespaces: []string{"namespace-1"}, - expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, + expectErrs: []error{errors.New("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.WaitForDelete(rl, timeout) }, @@ -910,7 +910,7 @@ func TestStatusWaitMixedResources(t *testing.T) { name: "wait fails when namespace resource included", objManifests: []string{podNamespace1Manifest, namespaceManifest}, allowedNamespaces: []string{"namespace-1"}, - expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, + expectErrs: []error{errors.New("user does not have cluster-wide LIST permissions for cluster-scoped resources")}, testFunc: func(sw *statusWaiter, rl ResourceList, timeout time.Duration) error { return sw.Wait(rl, timeout) }, From 6524162a0e39bed187a16b692243703d78735471 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 26 Feb 2026 18:55:55 +0100 Subject: [PATCH 198/571] chore(internal): enable perfsprint linter #### Description enable perfsprint linter in internal/tlsutil Signed-off-by: Matthieu MOREL --- internal/tlsutil/tls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/tlsutil/tls.go b/internal/tlsutil/tls.go index 88f26d47b..e986e6909 100644 --- a/internal/tlsutil/tls.go +++ b/internal/tlsutil/tls.go @@ -112,7 +112,7 @@ func NewTLSConfig(options ...TLSConfigOption) (*tls.Config, error) { if len(to.caPEMBlock) > 0 { cp := x509.NewCertPool() if !cp.AppendCertsFromPEM(to.caPEMBlock) { - return nil, fmt.Errorf("failed to append certificates from pem block") + return nil, errors.New("failed to append certificates from pem block") } config.RootCAs = cp From 0fecfd04c2f9a748046a8421595f3b9da6c895c7 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 26 Feb 2026 18:55:55 +0100 Subject: [PATCH 199/571] chore(pkg): enable perfsprint linter #### Description enable perfsprint linter in pkg/cmd Signed-off-by: Matthieu MOREL --- pkg/action/hooks_test.go | 7 ++++--- pkg/cmd/create_test.go | 7 +++---- pkg/cmd/flags.go | 5 +++-- pkg/cmd/plugin_install.go | 5 +++-- pkg/cmd/repo_remove_test.go | 6 +++--- pkg/cmd/search_hub.go | 5 +++-- pkg/cmd/search_repo.go | 8 ++++---- pkg/engine/engine_test.go | 7 +++++-- pkg/registry/client_http_test.go | 5 ++--- pkg/registry/registry_test.go | 8 ++++---- 10 files changed, 34 insertions(+), 29 deletions(-) diff --git a/pkg/action/hooks_test.go b/pkg/action/hooks_test.go index cb529b125..4b9b5becb 100644 --- a/pkg/action/hooks_test.go +++ b/pkg/action/hooks_test.go @@ -19,6 +19,7 @@ package action import ( "bytes" "context" + "errors" "fmt" "io" "reflect" @@ -173,7 +174,7 @@ func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace str t.Helper() var expectedOutput string if shouldOutput { - expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace) + expectedOutput = "attempted to output logs for namespace: " + expectedNamespace } is := assert.New(t) instAction := installAction(t) @@ -200,13 +201,13 @@ func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace str t.Helper() var expectedOutput string if shouldOutput { - expectedOutput = fmt.Sprintf("attempted to output logs for namespace: %s", expectedNamespace) + expectedOutput = "attempted to output logs for namespace: " + expectedNamespace } is := assert.New(t) instAction := installAction(t) instAction.ReleaseName = "failed-hooks" failingClient := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failingClient.WatchUntilReadyError = fmt.Errorf("failed watch") + failingClient.WatchUntilReadyError = errors.New("failed watch") instAction.cfg.KubeClient = failingClient outBuffer := &bytes.Buffer{} failingClient.PrintingKubeClient = kubefake.PrintingKubeClient{Out: io.Discard, LogOutput: outBuffer} diff --git a/pkg/cmd/create_test.go b/pkg/cmd/create_test.go index 6e95094c7..554141933 100644 --- a/pkg/cmd/create_test.go +++ b/pkg/cmd/create_test.go @@ -17,7 +17,6 @@ limitations under the License. package cmd import ( - "fmt" "os" "path/filepath" "slices" @@ -139,11 +138,11 @@ func TestCreateStarterCmd(t *testing.T) { if tt.useAbsolutePath { starterArg = filepath.Join(starterchart, "starterchart") } - cmd := fmt.Sprintf("create --starter=%s", starterArg) + cmd := "create --starter=" + starterArg if tt.chartAPIVersion == "v3" { - cmd += fmt.Sprintf(" --chart-api-version=%s", chartv3.APIVersionV3) + cmd += " --chart-api-version=" + chartv3.APIVersionV3 } else { - cmd += fmt.Sprintf(" --chart-api-version=%s", chartv2.APIVersionV2) + cmd += " --chart-api-version=" + chartv2.APIVersionV2 } cmd += " " + cname diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index 6d9d117f8..5a220d1ce 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -17,6 +17,7 @@ limitations under the License. package cmd import ( + "errors" "flag" "fmt" "log" @@ -119,7 +120,7 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) { // value to the given format pointer func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) { cmd.Flags().VarP(newOutputValue(output.Table, varRef), outputFlag, "o", - fmt.Sprintf("prints the output in the specified format. Allowed values: %s", strings.Join(output.Formats(), ", "))) + "prints the output in the specified format. Allowed values: "+strings.Join(output.Formats(), ", ")) err := cmd.RegisterFlagCompletionFunc(outputFlag, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { var formatNames []string @@ -195,7 +196,7 @@ func (p *postRendererString) Set(val string) error { return nil } if p.options.pluginName != "" { - return fmt.Errorf("cannot specify --post-renderer flag more than once") + return errors.New("cannot specify --post-renderer flag more than once") } p.options.pluginName = val pr, err := postrenderer.NewPostRendererPlugin(p.options.settings, p.options.pluginName, p.options.args...) diff --git a/pkg/cmd/plugin_install.go b/pkg/cmd/plugin_install.go index efa9b466c..f79356849 100644 --- a/pkg/cmd/plugin_install.go +++ b/pkg/cmd/plugin_install.go @@ -16,6 +16,7 @@ limitations under the License. package cmd import ( + "errors" "fmt" "io" "log/slog" @@ -102,7 +103,7 @@ func (o *pluginInstallOptions) complete(args []string) error { func (o *pluginInstallOptions) newInstallerForSource() (installer.Installer, error) { // Check if source is an OCI registry reference - if strings.HasPrefix(o.source, fmt.Sprintf("%s://", registry.OCIScheme)) { + if strings.HasPrefix(o.source, registry.OCIScheme+"://") { // Build getter options for OCI options := []getter.Option{ getter.WithTLSClientConfig(o.certFile, o.keyFile, o.caFile), @@ -135,7 +136,7 @@ func (o *pluginInstallOptions) run(out io.Writer) error { } else if shouldVerify { // For remote installations, check if verification is supported if verifier, ok := i.(installer.Verifier); !ok || !verifier.SupportsVerification() { - return fmt.Errorf("plugin source does not support verification. Use --verify=false to skip verification") + return errors.New("plugin source does not support verification. Use --verify=false to skip verification") } } else { // User explicitly disabled verification diff --git a/pkg/cmd/repo_remove_test.go b/pkg/cmd/repo_remove_test.go index fce15bb73..f2641ccf0 100644 --- a/pkg/cmd/repo_remove_test.go +++ b/pkg/cmd/repo_remove_test.go @@ -196,15 +196,15 @@ func TestRepoRemoveCompletion(t *testing.T) { // and that port changes each time we run the test. tests := []cmdTestCase{{ name: "completion for repo remove", - cmd: fmt.Sprintf("%s __completeNoDesc repo remove ''", repoSetup), + cmd: repoSetup + " __completeNoDesc repo remove ''", golden: "output/repo_list_comp.txt", }, { name: "completion for repo remove, no filter", - cmd: fmt.Sprintf("%s __completeNoDesc repo remove fo", repoSetup), + cmd: repoSetup + " __completeNoDesc repo remove fo", golden: "output/repo_list_comp.txt", }, { name: "completion for repo remove repetition", - cmd: fmt.Sprintf("%s __completeNoDesc repo remove foo ''", repoSetup), + cmd: repoSetup + " __completeNoDesc repo remove foo ''", golden: "output/repo_repeat_comp.txt", }} for _, test := range tests { diff --git a/pkg/cmd/search_hub.go b/pkg/cmd/search_hub.go index 1fde69394..f9adb73f4 100644 --- a/pkg/cmd/search_hub.go +++ b/pkg/cmd/search_hub.go @@ -17,6 +17,7 @@ limitations under the License. package cmd import ( + "errors" "fmt" "io" "log/slog" @@ -136,7 +137,7 @@ func (h *hubSearchWriter) WriteTable(out io.Writer) error { if len(h.elements) == 0 { // Fail if no results found and --fail-on-no-result is enabled if h.failOnNoResult { - return fmt.Errorf("no results found") + return errors.New("no results found") } _, err := out.Write([]byte("No results found\n")) @@ -175,7 +176,7 @@ func (h *hubSearchWriter) WriteYAML(out io.Writer) error { func (h *hubSearchWriter) encodeByFormat(out io.Writer, format output.Format) error { // Fail if no results found and --fail-on-no-result is enabled if len(h.elements) == 0 && h.failOnNoResult { - return fmt.Errorf("no results found") + return errors.New("no results found") } // Initialize the array so no results returns an empty array instead of null diff --git a/pkg/cmd/search_repo.go b/pkg/cmd/search_repo.go index 68c7543ab..53626f1b6 100644 --- a/pkg/cmd/search_repo.go +++ b/pkg/cmd/search_repo.go @@ -216,7 +216,7 @@ func (r *repoSearchWriter) WriteTable(out io.Writer) error { if len(r.results) == 0 { // Fail if no results found and --fail-on-no-result is enabled if r.failOnNoResult { - return fmt.Errorf("no results found") + return errors.New("no results found") } _, err := out.Write([]byte("No results found\n")) @@ -245,7 +245,7 @@ func (r *repoSearchWriter) WriteYAML(out io.Writer) error { func (r *repoSearchWriter) encodeByFormat(out io.Writer, format output.Format) error { // Fail if no results found and --fail-on-no-result is enabled if len(r.results) == 0 && r.failOnNoResult { - return fmt.Errorf("no results found") + return errors.New("no results found") } // Initialize the array so no results returns an empty array instead of null @@ -307,7 +307,7 @@ func compListChartsOfRepo(repoName string, prefix string) []string { // Provide dynamic auto-completion for commands that operate on charts (e.g., helm show) // When true, the includeFiles argument indicates that completion should include local files (e.g., local charts) func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.ShellCompDirective) { - cobra.CompDebugln(fmt.Sprintf("compListCharts with toComplete %s", toComplete), settings.Debug) + cobra.CompDebugln("compListCharts with toComplete "+toComplete, settings.Debug) noSpace := false noFile := false @@ -323,7 +323,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell if len(repoInfo) > 1 { repoDesc = repoInfo[1] } - repoWithSlash := fmt.Sprintf("%s/", repo) + repoWithSlash := repo + "/" if strings.HasPrefix(toComplete, repoWithSlash) { // Must complete with charts within the specified repo. // Don't filter on toComplete to allow for shell fuzzy matching diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index c80c65c65..09edc3337 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -17,6 +17,7 @@ limitations under the License. package engine import ( + "errors" "fmt" "path" "strings" @@ -417,7 +418,7 @@ func TestRenderWithClientProvider_error(t *testing.T) { t: t, scheme: map[string]kindProps{ "v1/Error": { - shouldErr: fmt.Errorf("kaboom"), + shouldErr: errors.New("kaboom"), }, }, } @@ -1059,9 +1060,11 @@ func TestRenderRecursionLimit(t *testing.T) { } var expect string + var expectSb1062 strings.Builder for range times { - expect += phrase + "\n" + expectSb1062.WriteString(phrase + "\n") } + expect += expectSb1062.String() if got := out["overlook/templates/quote"]; got != expect { t.Errorf("Expected %q, got %q (%v)", expect, got, out) } diff --git a/pkg/registry/client_http_test.go b/pkg/registry/client_http_test.go index 546d837d5..1c6751559 100644 --- a/pkg/registry/client_http_test.go +++ b/pkg/registry/client_http_test.go @@ -18,7 +18,6 @@ package registry import ( "errors" - "fmt" "os" "testing" @@ -65,7 +64,7 @@ func (suite *HTTPRegistryClientTestSuite) Test_3_Tags() { } func (suite *HTTPRegistryClientTestSuite) Test_4_ManInTheMiddle() { - ref := fmt.Sprintf("%s/testrepo/supposedlysafechart:9.9.9", suite.CompromisedRegistryHost) + ref := suite.CompromisedRegistryHost + "/testrepo/supposedlysafechart:9.9.9" // returns content that does not match the expected digest _, err := suite.RegistryClient.Pull(ref) @@ -74,7 +73,7 @@ func (suite *HTTPRegistryClientTestSuite) Test_4_ManInTheMiddle() { } func (suite *HTTPRegistryClientTestSuite) Test_5_ImageIndex() { - ref := fmt.Sprintf("%s/testrepo/image-index:0.1.0", suite.FakeRegistryHost) + ref := suite.FakeRegistryHost + "/testrepo/image-index:0.1.0" _, err := suite.RegistryClient.Pull(ref) suite.Nil(err) diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index 22b74c32e..b0c08fa3c 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -209,7 +209,7 @@ func initCompromisedRegistryTestServer() string { })) u, _ := url.Parse(s.URL) - return fmt.Sprintf("localhost:%s", u.Port()) + return "localhost:" + u.Port() } func initFakeRegistryTestServer() string { @@ -376,7 +376,7 @@ func initFakeRegistryTestServer() string { })) u, _ := url.Parse(s.URL) - return fmt.Sprintf("localhost:%s", u.Port()) + return "localhost:" + u.Port() } func testPush(suite *TestRegistry) { @@ -384,7 +384,7 @@ func testPush(suite *TestRegistry) { testingChartCreationTime := "1977-09-02T22:04:05Z" // Bad bytes - ref := fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost) + ref := suite.DockerRegistryHost + "/testrepo/testchart:1.2.3" _, err := suite.RegistryClient.Push([]byte("hello"), ref, PushOptCreationTime(testingChartCreationTime)) suite.NotNil(err, "error pushing non-chart bytes") @@ -468,7 +468,7 @@ func testPush(suite *TestRegistry) { func testPull(suite *TestRegistry) { // bad/missing ref - ref := fmt.Sprintf("%s/testrepo/no-existy:1.2.3", suite.DockerRegistryHost) + ref := suite.DockerRegistryHost + "/testrepo/no-existy:1.2.3" _, err := suite.RegistryClient.Pull(ref) suite.NotNil(err, "error on bad/missing ref") From c25c988cfb7bbe3b139dec39cad1db4be33b13c6 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 26 Feb 2026 18:55:55 +0100 Subject: [PATCH 200/571] chore(pkg): enable perfsprint linter #### Description enable perfsprint linter in pkg/registry Signed-off-by: Matthieu MOREL --- pkg/action/dependency.go | 2 +- pkg/chart/v2/lint/rules/values.go | 3 ++- pkg/cli/environment.go | 3 +-- pkg/cmd/push.go | 2 +- pkg/getter/ocigetter.go | 2 +- pkg/kube/client_test.go | 3 +-- pkg/kube/fake/printer.go | 3 +-- pkg/kube/wait.go | 2 +- pkg/registry/plugin.go | 3 ++- pkg/registry/reference.go | 3 +-- 10 files changed, 12 insertions(+), 14 deletions(-) diff --git a/pkg/action/dependency.go b/pkg/action/dependency.go index b12887bde..5c87f7cba 100644 --- a/pkg/action/dependency.go +++ b/pkg/action/dependency.go @@ -92,7 +92,7 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, p for _, arc := range archives { // we need to trip the prefix dirs and the extension off. filename = strings.TrimSuffix(filepath.Base(arc), ".tgz") - maybeVersion := strings.TrimPrefix(filename, fmt.Sprintf("%s-", dep.Name)) + maybeVersion := strings.TrimPrefix(filename, dep.Name+"-") if _, err := semver.StrictNewVersion(maybeVersion); err == nil { // If the version parsed without an error, it is possibly a valid diff --git a/pkg/chart/v2/lint/rules/values.go b/pkg/chart/v2/lint/rules/values.go index 8fe849c7a..2c766068c 100644 --- a/pkg/chart/v2/lint/rules/values.go +++ b/pkg/chart/v2/lint/rules/values.go @@ -17,6 +17,7 @@ limitations under the License. package rules import ( + "errors" "fmt" "os" "path/filepath" @@ -47,7 +48,7 @@ func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]any, func validateValuesFileExistence(valuesPath string) error { _, err := os.Stat(valuesPath) if err != nil { - return fmt.Errorf("file does not exist") + return errors.New("file does not exist") } return nil } diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 5c19734aa..83b6bdbba 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -24,7 +24,6 @@ These dependencies are expressed as interfaces so that alternate implementations package cli import ( - "fmt" "net/http" "os" "strconv" @@ -246,7 +245,7 @@ func (s *EnvSettings) EnvVars() map[string]string { "HELM_CACHE_HOME": helmpath.CachePath(""), "HELM_CONFIG_HOME": helmpath.ConfigPath(""), "HELM_DATA_HOME": helmpath.DataPath(""), - "HELM_DEBUG": fmt.Sprint(s.Debug), + "HELM_DEBUG": strconv.FormatBool(s.Debug), "HELM_PLUGINS": s.PluginsDirectory, "HELM_REGISTRY_CONFIG": s.RegistryConfig, "HELM_REPOSITORY_CACHE": s.RepositoryCache, diff --git a/pkg/cmd/push.go b/pkg/cmd/push.go index f57a7c52f..94c5732ff 100644 --- a/pkg/cmd/push.go +++ b/pkg/cmd/push.go @@ -62,7 +62,7 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { var comps []string for _, p := range providers { for _, scheme := range p.Schemes { - comps = append(comps, fmt.Sprintf("%s://", scheme)) + comps = append(comps, scheme+"://") } } return comps, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace diff --git a/pkg/getter/ocigetter.go b/pkg/getter/ocigetter.go index 24fc60c56..de8643bcd 100644 --- a/pkg/getter/ocigetter.go +++ b/pkg/getter/ocigetter.go @@ -58,7 +58,7 @@ func (g *OCIGetter) get(href string) (*bytes.Buffer, error) { client = c } - ref := strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme)) + ref := strings.TrimPrefix(href, registry.OCIScheme+"://") if version := g.opts.version; version != "" && !strings.Contains(path.Base(ref), ":") { ref = fmt.Sprintf("%s:%s", ref, version) diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 7e4cf046c..31894f68e 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -20,7 +20,6 @@ import ( "bytes" "context" "errors" - "fmt" "io" "net/http" "strings" @@ -1434,7 +1433,7 @@ func TestIsIncompatibleServerError(t *testing.T) { Want: false, }, "Generic error": { - Err: fmt.Errorf("some generic error"), + Err: errors.New("some generic error"), Want: false, }, } diff --git a/pkg/kube/fake/printer.go b/pkg/kube/fake/printer.go index e3fa11576..40897f0a7 100644 --- a/pkg/kube/fake/printer.go +++ b/pkg/kube/fake/printer.go @@ -17,7 +17,6 @@ limitations under the License. package fake import ( - "fmt" "io" "strings" "time" @@ -133,7 +132,7 @@ func (p *PrintingKubeClient) GetPodList(_ string, _ metav1.ListOptions) (*v1.Pod // OutputContainerLogsForPodList implements KubeClient OutputContainerLogsForPodList. func (p *PrintingKubeClient) OutputContainerLogsForPodList(_ *v1.PodList, someNamespace string, _ func(namespace, pod, container string) io.Writer) error { - _, err := io.Copy(p.LogOutput, strings.NewReader(fmt.Sprintf("attempted to output logs for namespace: %s", someNamespace))) + _, err := io.Copy(p.LogOutput, strings.NewReader("attempted to output logs for namespace: "+someNamespace)) return err } diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go index 8b4b2ed1b..75e889cdb 100644 --- a/pkg/kube/wait.go +++ b/pkg/kube/wait.go @@ -246,7 +246,7 @@ func (hw *legacyWaiter) watchUntilReady(timeout time.Duration, info *resource.In // Use a selector on the name of the resource. This should be unique for the // given version and kind - selector, err := fields.ParseSelector(fmt.Sprintf("metadata.name=%s", info.Name)) + selector, err := fields.ParseSelector("metadata.name=" + info.Name) if err != nil { return err } diff --git a/pkg/registry/plugin.go b/pkg/registry/plugin.go index e4b4afa24..959faaa7d 100644 --- a/pkg/registry/plugin.go +++ b/pkg/registry/plugin.go @@ -18,6 +18,7 @@ package registry import ( "encoding/json" + "errors" "fmt" "strings" @@ -190,7 +191,7 @@ func GetPluginName(source string) (string, error) { // e.g., "ghcr.io/user/plugin-name:v1.0.0" -> Repository: "user/plugin-name" repository := ref.Repository if repository == "" { - return "", fmt.Errorf("invalid OCI reference: missing repository") + return "", errors.New("invalid OCI reference: missing repository") } // Get the last part of the repository path as the plugin name diff --git a/pkg/registry/reference.go b/pkg/registry/reference.go index 9a98cf5c9..455792539 100644 --- a/pkg/registry/reference.go +++ b/pkg/registry/reference.go @@ -17,7 +17,6 @@ limitations under the License. package registry import ( - "fmt" "strings" "oras.land/oras-go/v2/registry" @@ -80,5 +79,5 @@ func (r *reference) String() string { // IsOCI determines whether a URL is to be treated as an OCI URL func IsOCI(url string) bool { - return strings.HasPrefix(url, fmt.Sprintf("%s://", OCIScheme)) + return strings.HasPrefix(url, OCIScheme+"://") } From 63f03c0f5c41b53de3d432b446da9430da99f5bd Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 26 Feb 2026 18:55:55 +0100 Subject: [PATCH 201/571] chore(pkg): enable perfsprint linter #### Description enable perfsprint linter in pkg/action Signed-off-by: Matthieu MOREL --- pkg/action/hooks.go | 4 ++-- pkg/action/validate.go | 3 ++- pkg/chart/common.go | 3 +-- pkg/cli/output/output.go | 3 ++- pkg/cli/values/options_test.go | 3 +-- pkg/cmd/require/args_test.go | 4 ++-- pkg/downloader/cache.go | 3 ++- pkg/provenance/sign_test.go | 4 ++-- pkg/pusher/ocipusher.go | 4 ++-- pkg/repo/v1/index_test.go | 3 +-- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index 28033395b..a4a8da7a6 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -257,9 +257,9 @@ func (cfg *Configuration) outputLogsByPolicy(h *release.Hook, releaseNamespace s } switch h.Kind { case "Job": - return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{LabelSelector: fmt.Sprintf("job-name=%s", h.Name)}) + return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{LabelSelector: "job-name=" + h.Name}) case "Pod": - return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{FieldSelector: fmt.Sprintf("metadata.name=%s", h.Name)}) + return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{FieldSelector: "metadata.name=" + h.Name}) default: return nil } diff --git a/pkg/action/validate.go b/pkg/action/validate.go index 5c48a628c..94bf4906b 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -17,6 +17,7 @@ limitations under the License. package action import ( + "errors" "fmt" "maps" @@ -226,7 +227,7 @@ func validateNameAndGenerateName(info *resource.Info) (bool, error) { } if info.Name != "" && accessor.GetGenerateName() != "" { - return true, fmt.Errorf("metadata.name and metadata.generateName cannot both be set") + return true, errors.New("metadata.name and metadata.generateName cannot both be set") } return false, nil diff --git a/pkg/chart/common.go b/pkg/chart/common.go index 288227953..cec2c7091 100644 --- a/pkg/chart/common.go +++ b/pkg/chart/common.go @@ -17,7 +17,6 @@ package chart import ( "errors" - "fmt" "log/slog" "reflect" "strings" @@ -192,7 +191,7 @@ func structToMap(obj any) (map[string]any, error) { // Check if the input is a struct if objValue.Kind() != reflect.Struct { - return nil, fmt.Errorf("input must be a struct or a pointer to a struct") + return nil, errors.New("input must be a struct or a pointer to a struct") } result := make(map[string]any) diff --git a/pkg/cli/output/output.go b/pkg/cli/output/output.go index 6364c90c3..a9bd846fe 100644 --- a/pkg/cli/output/output.go +++ b/pkg/cli/output/output.go @@ -18,6 +18,7 @@ package output import ( "encoding/json" + "errors" "fmt" "io" @@ -50,7 +51,7 @@ func FormatsWithDesc() map[string]string { } // ErrInvalidFormatType is returned when an unsupported format type is used -var ErrInvalidFormatType = fmt.Errorf("invalid format type") +var ErrInvalidFormatType = errors.New("invalid format type") // String returns the string representation of the Format func (o Format) String() string { diff --git a/pkg/cli/values/options_test.go b/pkg/cli/values/options_test.go index b8b1499a0..25046214d 100644 --- a/pkg/cli/values/options_test.go +++ b/pkg/cli/values/options_test.go @@ -19,7 +19,6 @@ package values import ( "bytes" "errors" - "fmt" "os" "path/filepath" "reflect" @@ -265,7 +264,7 @@ func TestReadFileErrorMessages(t *testing.T) { { name: "getter error with message", filePath: "http://example.com/file", - providers: getter.Providers{mockProvider([]string{"http"}, nil, fmt.Errorf("connection refused"))}, + providers: getter.Providers{mockProvider([]string{"http"}, nil, errors.New("connection refused"))}, wantErr: "connection refused", }, } diff --git a/pkg/cmd/require/args_test.go b/pkg/cmd/require/args_test.go index b6c430fc0..89403140e 100644 --- a/pkg/cmd/require/args_test.go +++ b/pkg/cmd/require/args_test.go @@ -16,8 +16,8 @@ limitations under the License. package require import ( - "fmt" "io" + "strconv" "strings" "testing" @@ -65,7 +65,7 @@ type testCase struct { func runTestCases(t *testing.T, testCases []testCase) { t.Helper() for i, tc := range testCases { - t.Run(fmt.Sprint(i), func(t *testing.T) { + t.Run(strconv.Itoa(i), func(t *testing.T) { cmd := &cobra.Command{ Use: "root", Run: func(*cobra.Command, []string) {}, diff --git a/pkg/downloader/cache.go b/pkg/downloader/cache.go index cecfc8bd7..1e23fbfcd 100644 --- a/pkg/downloader/cache.go +++ b/pkg/downloader/cache.go @@ -17,6 +17,7 @@ package downloader import ( "crypto/sha256" + "encoding/hex" "errors" "fmt" "io" @@ -85,5 +86,5 @@ func (c *DiskCache) Put(key [sha256.Size]byte, data io.Reader, cacheType string) // fileName generates the filename in a structured manner where the first part is the // directory and the full hash is the filename. func (c *DiskCache) fileName(id [sha256.Size]byte, cacheType string) string { - return filepath.Join(c.Root, fmt.Sprintf("%02x", id[0]), fmt.Sprintf("%x", id)+cacheType) + return filepath.Join(c.Root, fmt.Sprintf("%02x", id[0]), hex.EncodeToString(id[:])+cacheType) } diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go index eef01c52e..c4dda887b 100644 --- a/pkg/provenance/sign_test.go +++ b/pkg/provenance/sign_test.go @@ -17,7 +17,7 @@ package provenance import ( "crypto" - "fmt" + "errors" "io" "os" "path/filepath" @@ -330,7 +330,7 @@ func (s failSigner) Public() crypto.PublicKey { } func (s failSigner) Sign(_ io.Reader, _ []byte, _ crypto.SignerOpts) ([]byte, error) { - return nil, fmt.Errorf("always fails") + return nil, errors.New("always fails") } func TestClearSignError(t *testing.T) { diff --git a/pkg/pusher/ocipusher.go b/pkg/pusher/ocipusher.go index f03188391..2a12e09b4 100644 --- a/pkg/pusher/ocipusher.go +++ b/pkg/pusher/ocipusher.go @@ -76,7 +76,7 @@ func (pusher *OCIPusher) push(chartRef, href string) error { } var pushOpts []registry.PushOption - provRef := fmt.Sprintf("%s.prov", chartRef) + provRef := chartRef + ".prov" if _, err := os.Stat(provRef); err == nil { provBytes, err := os.ReadFile(provRef) if err != nil { @@ -86,7 +86,7 @@ func (pusher *OCIPusher) push(chartRef, href string) error { } ref := fmt.Sprintf("%s:%s", - path.Join(strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme)), meta.Metadata.Name), + path.Join(strings.TrimPrefix(href, registry.OCIScheme+"://"), meta.Metadata.Name), meta.Metadata.Version) // The time the chart was "created" is semantically the time the chart archive file was last written(modified) diff --git a/pkg/repo/v1/index_test.go b/pkg/repo/v1/index_test.go index 446160143..9ce3c090f 100644 --- a/pkg/repo/v1/index_test.go +++ b/pkg/repo/v1/index_test.go @@ -21,7 +21,6 @@ import ( "bytes" "encoding/json" "errors" - "fmt" "net/http" "os" "path/filepath" @@ -611,7 +610,7 @@ func TestIgnoreSkippableChartValidationError(t *testing.T) { Input: nil, }, "generic_error": { - Input: fmt.Errorf("foo"), + Input: errors.New("foo"), }, "non_skipped_validation_error": { Input: chart.ValidationError("chart.metadata.type must be application or library"), From 1d2d63cc4330fcac786e70926f805b69c0b49ca2 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 26 Feb 2026 18:55:56 +0100 Subject: [PATCH 202/571] chore(pkg): enable perfsprint linter #### Description enable perfsprint linter in pkg/cmd Signed-off-by: Matthieu MOREL --- pkg/action/uninstall_test.go | 5 ++--- pkg/chart/common/capabilities.go | 4 ++-- pkg/chart/v2/lint/rules/crds.go | 5 ++--- pkg/chart/v2/util/save_test.go | 4 ++-- pkg/cmd/plugin_verify.go | 5 +++-- pkg/cmd/status.go | 4 ++-- pkg/kube/wait_test.go | 4 ++-- pkg/release/v1/mock.go | 4 ++-- pkg/storage/driver/cfgmaps.go | 4 ++-- pkg/storage/driver/secrets.go | 4 ++-- 10 files changed, 21 insertions(+), 22 deletions(-) diff --git a/pkg/action/uninstall_test.go b/pkg/action/uninstall_test.go index b5a76d983..aeac98142 100644 --- a/pkg/action/uninstall_test.go +++ b/pkg/action/uninstall_test.go @@ -19,7 +19,6 @@ package action import ( "context" "errors" - "fmt" "io" "testing" @@ -115,7 +114,7 @@ func TestUninstallRelease_Wait(t *testing.T) { }` require.NoError(t, unAction.cfg.Releases.Create(rel)) failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitForDeleteError = fmt.Errorf("U timed out") + failer.WaitForDeleteError = errors.New("U timed out") unAction.cfg.KubeClient = failer resi, err := unAction.Run(rel.Name) is.Error(err) @@ -149,7 +148,7 @@ func TestUninstallRelease_Cascade(t *testing.T) { }` require.NoError(t, unAction.cfg.Releases.Create(rel)) failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.DeleteError = fmt.Errorf("Uninstall with cascade failed") + failer.DeleteError = errors.New("Uninstall with cascade failed") failer.BuildDummy = true unAction.cfg.KubeClient = failer _, err := unAction.Run(rel.Name) diff --git a/pkg/chart/common/capabilities.go b/pkg/chart/common/capabilities.go index 48d6e993f..20f4953cf 100644 --- a/pkg/chart/common/capabilities.go +++ b/pkg/chart/common/capabilities.go @@ -173,8 +173,8 @@ func newCapabilities(kubeVersionMajor, kubeVersionMinor uint64) (*Capabilities, KubeVersion: KubeVersion{ Version: version, normalizedVersion: version, - Major: fmt.Sprintf("%d", kubeVersionMajor), - Minor: fmt.Sprintf("%d", kubeVersionMinor), + Major: strconv.FormatUint(kubeVersionMajor, 10), + Minor: strconv.FormatUint(kubeVersionMinor, 10), }, APIVersions: DefaultVersionSet, HelmVersion: helmversion.Get(), diff --git a/pkg/chart/v2/lint/rules/crds.go b/pkg/chart/v2/lint/rules/crds.go index 4bb4d370b..1cf16b42d 100644 --- a/pkg/chart/v2/lint/rules/crds.go +++ b/pkg/chart/v2/lint/rules/crds.go @@ -19,7 +19,6 @@ package rules import ( "bytes" "errors" - "fmt" "io" "io/fs" "os" @@ -102,14 +101,14 @@ func validateCrdsDir(crdsPath string) error { func validateCrdAPIVersion(obj *k8sYamlStruct) error { if !strings.HasPrefix(obj.APIVersion, "apiextensions.k8s.io") { - return fmt.Errorf("apiVersion is not in 'apiextensions.k8s.io'") + return errors.New("apiVersion is not in 'apiextensions.k8s.io'") } return nil } func validateCrdKind(obj *k8sYamlStruct) error { if obj.Kind != "CustomResourceDefinition" { - return fmt.Errorf("object kind is not 'CustomResourceDefinition'") + return errors.New("object kind is not 'CustomResourceDefinition'") } return nil } diff --git a/pkg/chart/v2/util/save_test.go b/pkg/chart/v2/util/save_test.go index f8e137d0c..efb03842e 100644 --- a/pkg/chart/v2/util/save_test.go +++ b/pkg/chart/v2/util/save_test.go @@ -21,8 +21,8 @@ import ( "bytes" "compress/gzip" "crypto/sha256" + "encoding/hex" "errors" - "fmt" "io" "os" "path" @@ -357,5 +357,5 @@ func sha256Sum(filePath string) (string, error) { return "", err } - return fmt.Sprintf("%x", h.Sum(nil)), nil + return hex.EncodeToString(h.Sum(nil)), nil } diff --git a/pkg/cmd/plugin_verify.go b/pkg/cmd/plugin_verify.go index 5f89e743e..fc54a9d77 100644 --- a/pkg/cmd/plugin_verify.go +++ b/pkg/cmd/plugin_verify.go @@ -16,6 +16,7 @@ limitations under the License. package cmd import ( + "errors" "fmt" "io" "os" @@ -75,12 +76,12 @@ func (o *pluginVerifyOptions) run(out io.Writer) error { // Only support tarball verification if fi.IsDir() { - return fmt.Errorf("directory verification not supported - only plugin tarballs can be verified") + return errors.New("directory verification not supported - only plugin tarballs can be verified") } // Verify it's a tarball if !plugin.IsTarball(o.pluginPath) { - return fmt.Errorf("plugin file must be a gzipped tarball (.tar.gz or .tgz)") + return errors.New("plugin file must be a gzipped tarball (.tar.gz or .tgz)") } // Look for provenance file diff --git a/pkg/cmd/status.go b/pkg/cmd/status.go index f68316c6c..0d652ebd6 100644 --- a/pkg/cmd/status.go +++ b/pkg/cmd/status.go @@ -197,8 +197,8 @@ func (s statusPrinter) WriteTable(out io.Writer) error { } _, _ = fmt.Fprintf(out, "TEST SUITE: %s\n%s\n%s\n%s\n", h.Name, - fmt.Sprintf("Last Started: %s", h.LastRun.StartedAt.Format(time.ANSIC)), - fmt.Sprintf("Last Completed: %s", h.LastRun.CompletedAt.Format(time.ANSIC)), + "Last Started: "+h.LastRun.StartedAt.Format(time.ANSIC), + "Last Completed: "+h.LastRun.CompletedAt.Format(time.ANSIC), fmt.Sprintf("Phase: %s", h.LastRun.Phase), ) } diff --git a/pkg/kube/wait_test.go b/pkg/kube/wait_test.go index 1be1d30b9..b088dc5a3 100644 --- a/pkg/kube/wait_test.go +++ b/pkg/kube/wait_test.go @@ -17,7 +17,7 @@ limitations under the License. package kube import ( - "fmt" + "errors" "net/http" "strings" "testing" @@ -451,7 +451,7 @@ func TestLegacyWaiter_isRetryableError(t *testing.T) { }, { name: "non-status error", - err: fmt.Errorf("some generic error"), + err: errors.New("some generic error"), wantRetry: true, }, } diff --git a/pkg/release/v1/mock.go b/pkg/release/v1/mock.go index a26044bd7..fc98a4525 100644 --- a/pkg/release/v1/mock.go +++ b/pkg/release/v1/mock.go @@ -17,8 +17,8 @@ limitations under the License. package v1 import ( - "fmt" "math/rand" + "strconv" "time" "helm.sh/helm/v4/pkg/chart/common" @@ -57,7 +57,7 @@ func Mock(opts *MockReleaseOptions) *Release { name := opts.Name if name == "" { - name = "testrelease-" + fmt.Sprint(rand.Intn(100)) + name = "testrelease-" + strconv.Itoa(rand.Intn(100)) } version := 1 diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go index f82ade5e9..00a0832b3 100644 --- a/pkg/storage/driver/cfgmaps.go +++ b/pkg/storage/driver/cfgmaps.go @@ -171,7 +171,7 @@ func (cfgmaps *ConfigMaps) Create(key string, rls release.Releaser) error { lbs.init() lbs.fromMap(rac.Labels()) - lbs.set("createdAt", fmt.Sprintf("%v", time.Now().Unix())) + lbs.set("createdAt", strconv.FormatInt(time.Now().Unix(), 10)) rel, err := releaserToV1Release(rls) if err != nil { @@ -209,7 +209,7 @@ func (cfgmaps *ConfigMaps) Update(key string, rel release.Releaser) error { lbs.init() lbs.fromMap(rls.Labels) - lbs.set("modifiedAt", fmt.Sprintf("%v", time.Now().Unix())) + lbs.set("modifiedAt", strconv.FormatInt(time.Now().Unix(), 10)) // create a new configmap object to hold the release obj, err := newConfigMapsObject(key, rls, lbs) diff --git a/pkg/storage/driver/secrets.go b/pkg/storage/driver/secrets.go index a73f3cf05..5e12684df 100644 --- a/pkg/storage/driver/secrets.go +++ b/pkg/storage/driver/secrets.go @@ -171,7 +171,7 @@ func (secrets *Secrets) Create(key string, rel release.Releaser) error { lbs.init() lbs.fromMap(rls.Labels) - lbs.set("createdAt", fmt.Sprintf("%v", time.Now().Unix())) + lbs.set("createdAt", strconv.FormatInt(time.Now().Unix(), 10)) // create a new secret to hold the release obj, err := newSecretsObject(key, rls, lbs) @@ -202,7 +202,7 @@ func (secrets *Secrets) Update(key string, rel release.Releaser) error { lbs.init() lbs.fromMap(rls.Labels) - lbs.set("modifiedAt", fmt.Sprintf("%v", time.Now().Unix())) + lbs.set("modifiedAt", strconv.FormatInt(time.Now().Unix(), 10)) // create a new secret object to hold the release obj, err := newSecretsObject(key, rls, lbs) From e3c74fd9fae52c85899ee0ca9a0c1422d59e2bc2 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 26 Feb 2026 18:55:56 +0100 Subject: [PATCH 203/571] chore(pkg): enable perfsprint linter #### Description enable perfsprint linter in pkg/storage/driver Signed-off-by: Matthieu MOREL --- pkg/storage/driver/sql_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go index a1847e199..4b4686b66 100644 --- a/pkg/storage/driver/sql_test.go +++ b/pkg/storage/driver/sql_test.go @@ -290,7 +290,7 @@ func TestSqlCreateAlreadyExists(t *testing.T) { mock. ExpectExec(regexp.QuoteMeta(insertQuery)). WithArgs(key, sqlReleaseDefaultType, body, rel.Name, rel.Namespace, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, recentUnixTimestamp()). - WillReturnError(fmt.Errorf("dialect dependent SQL error")) + WillReturnError(errors.New("dialect dependent SQL error")) selectQuery := fmt.Sprintf( regexp.QuoteMeta("SELECT %s FROM %s WHERE %s = $1 AND %s = $2"), From dc0e3f10c3ba8f25aa71c523d2e273690d338a17 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 26 Feb 2026 18:55:56 +0100 Subject: [PATCH 204/571] chore(pkg): fix perfsprint linter issues part 6 Signed-off-by: Matthieu MOREL --- pkg/action/install.go | 2 +- pkg/cmd/history.go | 2 +- pkg/cmd/list.go | 2 +- pkg/cmd/package.go | 2 +- pkg/cmd/plugin_package.go | 2 +- pkg/cmd/plugin_test.go | 2 +- pkg/downloader/manager.go | 2 +- pkg/getter/httpgetter_test.go | 2 +- pkg/registry/client.go | 2 +- pkg/repo/v1/repotest/server.go | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index 2f747a789..50df13c05 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -377,7 +377,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st } // Check error from render if err != nil { - rel.SetStatus(rcommon.StatusFailed, fmt.Sprintf("failed to render resource: %s", err.Error())) + rel.SetStatus(rcommon.StatusFailed, "failed to render resource: "+err.Error()) // Return a release with partial data so that the client can show debugging information. return rel, err } diff --git a/pkg/cmd/history.go b/pkg/cmd/history.go index 11cb7afb5..3349b7bc1 100644 --- a/pkg/cmd/history.go +++ b/pkg/cmd/history.go @@ -260,7 +260,7 @@ func compListRevisions(_ string, cfg *action.Configuration, releaseName string) return nil, cobra.ShellCompDirectiveError } for _, version := range hist { - appVersion := fmt.Sprintf("App: %s", version.Chart.Metadata.AppVersion) + appVersion := "App: " + version.Chart.Metadata.AppVersion chartDesc := fmt.Sprintf("Chart: %s-%s", version.Chart.Metadata.Name, version.Chart.Metadata.Version) revisions = append(revisions, fmt.Sprintf("%s\t%s, %s", strconv.Itoa(version.Version), appVersion, chartDesc)) } diff --git a/pkg/cmd/list.go b/pkg/cmd/list.go index 3c15a0954..54becddca 100644 --- a/pkg/cmd/list.go +++ b/pkg/cmd/list.go @@ -254,7 +254,7 @@ func filterReleases(releases []*release.Release, ignoredReleaseNames []string) [ // Provide dynamic auto-completion for release names func compListReleases(toComplete string, ignoredReleaseNames []string, cfg *action.Configuration) ([]string, cobra.ShellCompDirective) { - cobra.CompDebugln(fmt.Sprintf("compListReleases with toComplete %s", toComplete), settings.Debug) + cobra.CompDebugln("compListReleases with toComplete "+toComplete, settings.Debug) client := action.NewList(cfg) client.All = true diff --git a/pkg/cmd/package.go b/pkg/cmd/package.go index 96c0c47b2..14f9c8425 100644 --- a/pkg/cmd/package.go +++ b/pkg/cmd/package.go @@ -57,7 +57,7 @@ func newPackageCmd(out io.Writer) *cobra.Command { Long: packageDesc, RunE: func(_ *cobra.Command, args []string) error { if len(args) == 0 { - return fmt.Errorf("need at least one argument, the path to the chart") + return errors.New("need at least one argument, the path to the chart") } if client.Sign { if client.Key == "" { diff --git a/pkg/cmd/plugin_package.go b/pkg/cmd/plugin_package.go index 05f8bb5ad..6c7ca3050 100644 --- a/pkg/cmd/plugin_package.go +++ b/pkg/cmd/plugin_package.go @@ -81,7 +81,7 @@ func (o *pluginPackageOptions) run(out io.Writer) error { return err } if !fi.IsDir() { - return fmt.Errorf("plugin package only supports directories, not tarballs") + return errors.New("plugin package only supports directories, not tarballs") } // Load and validate plugin metadata diff --git a/pkg/cmd/plugin_test.go b/pkg/cmd/plugin_test.go index a250ba221..0c2d00137 100644 --- a/pkg/cmd/plugin_test.go +++ b/pkg/cmd/plugin_test.go @@ -100,7 +100,7 @@ func TestLoadCLIPlugins(t *testing.T) { "HELM_PLUGINS=testdata/helmhome/helm/plugins", "HELM_REPOSITORY_CONFIG=testdata/helmhome/helm/repositories.yaml", "HELM_REPOSITORY_CACHE=testdata/helmhome/helm/repository", - fmt.Sprintf("HELM_BIN=%s", os.Args[0]), + "HELM_BIN=" + os.Args[0], }, "\n") + "\n" // Test that the YAML file was correctly converted to a command. diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 6043fbaaa..fd4815cc4 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -53,7 +53,7 @@ type ErrRepoNotFound struct { // Error implements the error interface. func (e ErrRepoNotFound) Error() string { - return fmt.Sprintf("no repository definition for %s", strings.Join(e.Repos, ", ")) + return "no repository definition for " + strings.Join(e.Repos, ", ") } // Manager handles the lifecycle of fetching, resolving, and storing dependencies. diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index e7c3f3cb1..1582d9cfb 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -403,7 +403,7 @@ func TestDownloadTLSWithRedirect(t *testing.T) { // request URL for every request (including redirects). Setting `tls.Config.ServerName` on the // client just overrides the remote endpoint's hostname. // See https://github.com/golang/go/blob/3979fb9/src/net/http/transport.go#L1505-L1513. - u.Host = fmt.Sprintf("localhost:%s", u.Port()) + u.Host = "localhost:" + u.Port() http.Redirect(rw, r, u.String(), http.StatusTemporaryRedirect) })) diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 72ba0b540..62e560dba 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -882,7 +882,7 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (string, *ur tag = version } else { // Retrieve list of repository tags - tags, err := c.Tags(strings.TrimPrefix(ref, fmt.Sprintf("%s://", OCIScheme))) + tags, err := c.Tags(strings.TrimPrefix(ref, OCIScheme+"://")) if err != nil { return "", nil, err } diff --git a/pkg/repo/v1/repotest/server.go b/pkg/repo/v1/repotest/server.go index bb8c2e0bf..914bece80 100644 --- a/pkg/repo/v1/repotest/server.go +++ b/pkg/repo/v1/repotest/server.go @@ -249,7 +249,7 @@ func (srv *OCIServer) RunWithReturn(t *testing.T, opts ...OCIServerOpt) *OCIServ t.Fatalf("error logging into registry with good credentials: %v", err) } - ref := fmt.Sprintf("%s/u/ocitestuser/oci-dependent-chart:0.1.0", srv.RegistryURL) + ref := srv.RegistryURL + "/u/ocitestuser/oci-dependent-chart:0.1.0" err = chartutil.ExpandFile(srv.Dir, filepath.Join(srv.Dir, "oci-dependent-chart-0.1.0.tgz")) if err != nil { From 82d9bedea7d3e342011d82e2e11ff83b396dffbe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 21:33:16 +0000 Subject: [PATCH 205/571] chore(deps): bump actions/upload-artifact from 6.0.0 to 7.0.0 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6.0.0 to 7.0.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/b7c566a772e6b6bfb58ed0dc250532a479d7789f...bbbca2ddaa5d8feaa63e36b76fdaad77386f024f) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index dc2d3b3d9..d2bf4e56a 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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: SARIF file path: results.sarif From d4f6193a7ec7ae9ea479da3372eeaf22b445ebcc Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 27 Feb 2026 13:41:41 +0100 Subject: [PATCH 206/571] chore(internal): enable perfsprint linter (#31871) #### Description enable perfsprint linter in internal/plugin Signed-off-by: Matthieu MOREL --- internal/chart/v3/lint/rules/crds.go | 5 ++--- internal/plugin/installer/http_installer.go | 5 +++-- internal/plugin/installer/installer.go | 4 ++-- internal/plugin/metadata.go | 10 +++++----- internal/plugin/metadata_legacy.go | 5 +++-- internal/plugin/metadata_v1.go | 7 ++++--- internal/plugin/runtime_subprocess.go | 4 ++-- internal/plugin/subprocess_commands.go | 4 ++-- internal/release/v2/mock.go | 4 ++-- internal/version/clientgo.go | 6 +++--- 10 files changed, 28 insertions(+), 26 deletions(-) diff --git a/internal/chart/v3/lint/rules/crds.go b/internal/chart/v3/lint/rules/crds.go index deedeb0f2..0a479d214 100644 --- a/internal/chart/v3/lint/rules/crds.go +++ b/internal/chart/v3/lint/rules/crds.go @@ -19,7 +19,6 @@ package rules import ( "bytes" "errors" - "fmt" "io" "io/fs" "os" @@ -102,14 +101,14 @@ func validateCrdsDir(crdsPath string) error { func validateCrdAPIVersion(obj *k8sYamlStruct) error { if !strings.HasPrefix(obj.APIVersion, "apiextensions.k8s.io") { - return fmt.Errorf("apiVersion is not in 'apiextensions.k8s.io'") + return errors.New("apiVersion is not in 'apiextensions.k8s.io'") } return nil } func validateCrdKind(obj *k8sYamlStruct) error { if obj.Kind != "CustomResourceDefinition" { - return fmt.Errorf("object kind is not 'CustomResourceDefinition'") + return errors.New("object kind is not 'CustomResourceDefinition'") } return nil } diff --git a/internal/plugin/installer/http_installer.go b/internal/plugin/installer/http_installer.go index bb96314f4..5a2912d2e 100644 --- a/internal/plugin/installer/http_installer.go +++ b/internal/plugin/installer/http_installer.go @@ -17,6 +17,7 @@ package installer // import "helm.sh/helm/v4/internal/plugin/installer" import ( "bytes" + "errors" "fmt" "log/slog" "os" @@ -143,7 +144,7 @@ func (i *HTTPInstaller) Install() error { // Update updates a local repository // Not implemented for now since tarball most likely will be packaged by version func (i *HTTPInstaller) Update() error { - return fmt.Errorf("method Update() not implemented for HttpInstaller") + return errors.New("method Update() not implemented for HttpInstaller") } // Path is overridden because we want to join on the plugin name not the file name @@ -163,7 +164,7 @@ func (i *HTTPInstaller) SupportsVerification() bool { // GetVerificationData returns cached plugin and provenance data for verification func (i *HTTPInstaller) GetVerificationData() (archiveData, provData []byte, filename string, err error) { if !i.SupportsVerification() { - return nil, nil, "", fmt.Errorf("verification not supported for this source") + return nil, nil, "", errors.New("verification not supported for this source") } // Download plugin data once and cache it diff --git a/internal/plugin/installer/installer.go b/internal/plugin/installer/installer.go index e3975c2d7..f0870dcc5 100644 --- a/internal/plugin/installer/installer.go +++ b/internal/plugin/installer/installer.go @@ -87,7 +87,7 @@ func InstallWithOptions(i Installer, opts Options) (*VerificationResult, error) if opts.Verify { verifier, ok := i.(Verifier) if !ok || !verifier.SupportsVerification() { - return nil, fmt.Errorf("--verify is only supported for plugin tarballs (.tgz files)") + return nil, errors.New("--verify is only supported for plugin tarballs (.tgz files)") } // Get verification data (works for both memory and file-based installers) @@ -137,7 +137,7 @@ func Update(i Installer) error { // NewForSource determines the correct Installer for the given source. func NewForSource(source, version string) (installer Installer, err error) { - if strings.HasPrefix(source, fmt.Sprintf("%s://", registry.OCIScheme)) { + if strings.HasPrefix(source, registry.OCIScheme+"://") { // Source is an OCI registry reference installer, err = NewOCIInstaller(source) } else if isLocalReference(source) { diff --git a/internal/plugin/metadata.go b/internal/plugin/metadata.go index 4e019f0b3..ef7d54c3a 100644 --- a/internal/plugin/metadata.go +++ b/internal/plugin/metadata.go @@ -58,23 +58,23 @@ func (m Metadata) Validate() error { } if m.APIVersion == "" { - errs = append(errs, fmt.Errorf("empty APIVersion")) + errs = append(errs, errors.New("empty APIVersion")) } if m.Type == "" { - errs = append(errs, fmt.Errorf("empty type field")) + errs = append(errs, errors.New("empty type field")) } if m.Runtime == "" { - errs = append(errs, fmt.Errorf("empty runtime field")) + errs = append(errs, errors.New("empty runtime field")) } if m.Config == nil { - errs = append(errs, fmt.Errorf("missing config field")) + errs = append(errs, errors.New("missing config field")) } if m.RuntimeConfig == nil { - errs = append(errs, fmt.Errorf("missing runtimeConfig field")) + errs = append(errs, errors.New("missing runtimeConfig field")) } // Validate the config itself diff --git a/internal/plugin/metadata_legacy.go b/internal/plugin/metadata_legacy.go index 3cd1a50cd..b5849edeb 100644 --- a/internal/plugin/metadata_legacy.go +++ b/internal/plugin/metadata_legacy.go @@ -16,6 +16,7 @@ limitations under the License. package plugin import ( + "errors" "fmt" "strings" "unicode" @@ -74,11 +75,11 @@ func (m *MetadataLegacy) Validate() error { m.Usage = sanitizeString(m.Usage) if len(m.PlatformCommand) > 0 && len(m.Command) > 0 { - return fmt.Errorf("both platformCommand and command are set") + return errors.New("both platformCommand and command are set") } if len(m.PlatformHooks) > 0 && len(m.Hooks) > 0 { - return fmt.Errorf("both platformHooks and hooks are set") + return errors.New("both platformHooks and hooks are set") } // Validate downloader plugins diff --git a/internal/plugin/metadata_v1.go b/internal/plugin/metadata_v1.go index 81dbc2e20..77f23e154 100644 --- a/internal/plugin/metadata_v1.go +++ b/internal/plugin/metadata_v1.go @@ -16,6 +16,7 @@ limitations under the License. package plugin import ( + "errors" "fmt" ) @@ -48,7 +49,7 @@ type MetadataV1 struct { func (m *MetadataV1) Validate() error { if !validPluginName.MatchString(m.Name) { - return fmt.Errorf("invalid plugin `name`") + return errors.New("invalid plugin `name`") } if m.APIVersion != "v1" { @@ -56,11 +57,11 @@ func (m *MetadataV1) Validate() error { } if m.Type == "" { - return fmt.Errorf("`type` missing") + return errors.New("`type` missing") } if m.Runtime == "" { - return fmt.Errorf("`runtime` missing") + return errors.New("`runtime` missing") } return nil diff --git a/internal/plugin/runtime_subprocess.go b/internal/plugin/runtime_subprocess.go index c836c1c6d..cd1a0842c 100644 --- a/internal/plugin/runtime_subprocess.go +++ b/internal/plugin/runtime_subprocess.go @@ -117,8 +117,8 @@ func (r *SubprocessPluginRuntime) InvokeWithEnv(main string, argv []string, env cmd.Env = slices.Clone(os.Environ()) cmd.Env = append( cmd.Env, - fmt.Sprintf("HELM_PLUGIN_NAME=%s", r.metadata.Name), - fmt.Sprintf("HELM_PLUGIN_DIR=%s", r.pluginDir)) + "HELM_PLUGIN_NAME="+r.metadata.Name, + "HELM_PLUGIN_DIR="+r.pluginDir) cmd.Env = append(cmd.Env, env...) cmd.Stdin = stdin diff --git a/internal/plugin/subprocess_commands.go b/internal/plugin/subprocess_commands.go index 9a57ed891..211ce5ebf 100644 --- a/internal/plugin/subprocess_commands.go +++ b/internal/plugin/subprocess_commands.go @@ -16,7 +16,7 @@ limitations under the License. package plugin import ( - "fmt" + "errors" "os" "runtime" "strings" @@ -80,7 +80,7 @@ func getPlatformCommand(cmds []PlatformCommand) ([]string, []string) { func PrepareCommands(cmds []PlatformCommand, expandArgs bool, extraArgs []string, env map[string]string) (string, []string, error) { cmdParts, args := getPlatformCommand(cmds) if len(cmdParts) == 0 || cmdParts[0] == "" { - return "", nil, fmt.Errorf("no plugin command is applicable") + return "", nil, errors.New("no plugin command is applicable") } envMappingFunc := func(key string) string { return env[key] diff --git a/internal/release/v2/mock.go b/internal/release/v2/mock.go index 8a86d5edc..295eb219f 100644 --- a/internal/release/v2/mock.go +++ b/internal/release/v2/mock.go @@ -17,8 +17,8 @@ limitations under the License. package v2 import ( - "fmt" "math/rand" + "strconv" "time" v3 "helm.sh/helm/v4/internal/chart/v3" @@ -57,7 +57,7 @@ func Mock(opts *MockReleaseOptions) *Release { name := opts.Name if name == "" { - name = "testrelease-" + fmt.Sprint(rand.Intn(100)) + name = "testrelease-" + strconv.Itoa(rand.Intn(100)) } version := 1 diff --git a/internal/version/clientgo.go b/internal/version/clientgo.go index ab2a38fd5..50a4fd5cb 100644 --- a/internal/version/clientgo.go +++ b/internal/version/clientgo.go @@ -17,7 +17,7 @@ limitations under the License. package version import ( - "fmt" + "errors" "runtime/debug" "slices" @@ -27,7 +27,7 @@ import ( func K8sIOClientGoModVersion() (string, error) { info, ok := debug.ReadBuildInfo() if !ok { - return "", fmt.Errorf("failed to read build info") + return "", errors.New("failed to read build info") } idx := slices.IndexFunc(info.Deps, func(m *debug.Module) bool { @@ -35,7 +35,7 @@ func K8sIOClientGoModVersion() (string, error) { }) if idx == -1 { - return "", fmt.Errorf("k8s.io/client-go not found in build info") + return "", errors.New("k8s.io/client-go not found in build info") } m := info.Deps[idx] From 5d40f17011a477620841edb740d381a012716ae8 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Sat, 28 Feb 2026 11:11:02 +0100 Subject: [PATCH 207/571] Update pkg/cmd/status.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Matthieu MOREL --- pkg/cmd/status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/status.go b/pkg/cmd/status.go index 0d652ebd6..705691aaf 100644 --- a/pkg/cmd/status.go +++ b/pkg/cmd/status.go @@ -199,7 +199,7 @@ func (s statusPrinter) WriteTable(out io.Writer) error { h.Name, "Last Started: "+h.LastRun.StartedAt.Format(time.ANSIC), "Last Completed: "+h.LastRun.CompletedAt.Format(time.ANSIC), - fmt.Sprintf("Phase: %s", h.LastRun.Phase), + "Phase: "+h.LastRun.Phase, ) } } From 1dfa77ed8ba6f9e26542064248bc9eab40c1a662 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Tue, 3 Mar 2026 12:55:07 -0700 Subject: [PATCH 208/571] fix: bump fluxcd/cli-utils to v0.37.2-flux.1 Bumps fluxcd/cli-utils from v0.37.1-flux.1 to v0.37.2-flux.1, which includes a fix to exclude transient pod failures from controller status (fluxcd/cli-utils#22). This resolves intermittent failures with `helm upgrade --install` when using the watcher wait strategy, where StatefulSets were prematurely reported as Failed when pods were unschedulable (e.g., waiting for cluster autoscaler/Karpenter to provision nodes). The upstream fix treats unschedulable pods and pods being deleted during rolling updates as transient states rather than terminal failures. Fixes #31896 Signed-off-by: Terry Howe --- go.mod | 6 +++--- go.sum | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 6ab73f33a..a0fed3335 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.18.0 - github.com/fluxcd/cli-utils v0.37.1-flux.1 + github.com/fluxcd/cli-utils v0.37.2-flux.1 github.com/foxcpp/go-mockdns v1.2.0 github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.13.0 @@ -116,7 +116,7 @@ require ( github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/gomega v1.38.2 // indirect + github.com/onsi/gomega v1.39.0 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect @@ -175,7 +175,7 @@ require ( k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect - sigs.k8s.io/kustomize/api v0.20.1 // indirect + sigs.k8s.io/kustomize/api v0.21.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect ) diff --git a/go.sum b/go.sum index e512b6147..b0cea71db 100644 --- a/go.sum +++ b/go.sum @@ -91,8 +91,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/cli-utils v0.37.1-flux.1 h1:WnG2mHxCPZMj/soIq/S/1zvbrGCJN3GJGbNfG06X55M= -github.com/fluxcd/cli-utils v0.37.1-flux.1/go.mod h1:aND5wX3LuTFtB7eUT7vsWr8mmxRVSPR2Wkvbn0SqPfw= +github.com/fluxcd/cli-utils v0.37.2-flux.1 h1:tQ588ghtRN+E+kHq415FddfqA9v4brn/1WWgrP6rQR0= +github.com/fluxcd/cli-utils v0.37.2-flux.1/go.mod h1:LcWSu1NYET8d8U7O326RhEm5JkQXCMK6ITu4G1CT02c= github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -143,8 +143,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ= -github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= @@ -233,10 +233,10 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= -github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= -github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= -github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= +github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= +github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -507,8 +507,8 @@ sigs.k8s.io/controller-runtime v0.23.1 h1:TjJSM80Nf43Mg21+RCy3J70aj/W6KyvDtOlpKf sigs.k8s.io/controller-runtime v0.23.1/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= -sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= +sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= +sigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI= sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= From b550ce90946b3b47cecd290fc5d0eee637ddb531 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Tue, 3 Mar 2026 13:41:09 -0700 Subject: [PATCH 209/571] fix: bump go.opentelemetry.io/otel/sdk to v1.40.0 for GO-2026-4394 Bump the OpenTelemetry Go SDK and related packages to fix GO-2026-4394 (arbitrary code execution via PATH hijacking). The vulnerable sdk@v1.36.0 is replaced with sdk@v1.40.0. Signed-off-by: Terry Howe --- go.mod | 65 ++++++++++++++-------------- go.sum | 134 ++++++++++++++++++++++++++++++--------------------------- 2 files changed, 103 insertions(+), 96 deletions(-) diff --git a/go.mod b/go.mod index 6ab73f33a..576df9224 100644 --- a/go.mod +++ b/go.mod @@ -61,7 +61,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.6.1 // indirect @@ -92,7 +92,7 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect github.com/huandu/xstrings v1.5.0 // indirect @@ -121,8 +121,9 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.17.0 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/otlptranslator v1.0.0 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect github.com/redis/go-redis/v9 v9.7.3 // indirect @@ -133,41 +134,41 @@ require ( github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 // indirect - go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.54.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 // indirect - go.opentelemetry.io/otel/log v0.8.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/sdk v1.36.0 // indirect - go.opentelemetry.io/otel/sdk/log v0.8.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect - go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 // indirect + go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect + go.opentelemetry.io/otel v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.62.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 // indirect + go.opentelemetry.io/otel/log v0.16.0 // indirect + go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/sdk v1.40.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.16.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect + go.opentelemetry.io/otel/trace v1.40.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/mod v0.32.0 // indirect golang.org/x/net v0.49.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.41.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect - google.golang.org/grpc v1.72.2 // indirect - google.golang.org/protobuf v1.36.8 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/grpc v1.78.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index e512b6147..4bfd1c11f 100644 --- a/go.sum +++ b/go.sum @@ -42,8 +42,8 @@ github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdb github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -155,8 +155,8 @@ github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= @@ -260,13 +260,15 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= +github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= -github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= @@ -315,52 +317,54 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w= -go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= -go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= -go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= -go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= -go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0/go.mod h1:zKU4zUgKiaRxrdovSS2amdM5gOc59slmo/zJwGX+YBg= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 h1:SZmDnHcgp3zwlPBS2JX2urGYe/jBKEIT6ZedHRUyCz8= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0/go.mod h1:fdWW0HtZJ7+jNpTKUR0GpMEDP69nR8YBJQxNiVCE3jk= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsux7Qmq8ToKAx1XCilTQECZ0KDZyTw= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s= -go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= -go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= -go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 h1:I/7S/yWobR3QHFLqHsJ8QOndoiFsj1VgHpQiq43KlUI= +go.opentelemetry.io/contrib/bridges/prometheus v0.65.0/go.mod h1:jPF6gn3y1E+nozCAEQj3c6NZ8KY+tvAgSVfvoOJUFac= +go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 h1:2gApdml7SznX9szEKFjKjM4qGcGSvAybYLBY319XG3g= +go.opentelemetry.io/contrib/exporters/autoexport v0.65.0/go.mod h1:0QqAGlbHXhmPYACG3n5hNzO5DnEqqtg4VcK5pr22RI0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= +go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= +go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 h1:ZVg+kCXxd9LtAaQNKBxAvJ5NpMf7LpvEr4MIZqb0TMQ= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0/go.mod h1:hh0tMeZ75CCXrHd9OXRYxTlCAdxcXioWHFIpYw2rZu8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 h1:djrxvDxAe44mJUrKataUbOhCKhR3F8QCyWucO16hTQs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0/go.mod h1:dt3nxpQEiSoKvfTVxp3TUg5fHPLhKtbcnN3Z1I1ePD0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 h1:NOyNnS19BF2SUDApbOKbDtWZ0IK7b8FJ2uAGdIWOGb0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0/go.mod h1:VL6EgVikRLcJa9ftukrHu/ZkkhFBSo1lzvdBC9CF1ss= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 h1:9y5sHvAxWzft1WQ4BwqcvA+IFVUJ1Ya75mSAUnFEVwE= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0/go.mod h1:eQqT90eR3X5Dbs1g9YSM30RavwLF725Ris5/XSXWvqE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= +go.opentelemetry.io/otel/exporters/prometheus v0.62.0 h1:krvC4JMfIOVdEuNPTtQ0ZjCiXrybhv+uOHMfHRmnvVo= +go.opentelemetry.io/otel/exporters/prometheus v0.62.0/go.mod h1:fgOE6FM/swEnsVQCqCnbOfRV4tOnWPg7bVeo4izBuhQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 h1:ivlbaajBWJqhcCPniDqDJmRwj4lc6sRT+dCAVKNmxlQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0/go.mod h1:u/G56dEKDDwXNCVLsbSrllB2o8pbtFLUC4HpR66r2dc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= +go.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4= +go.opentelemetry.io/otel/log v0.16.0/go.mod h1:rWsmqNVTLIA8UnwYVOItjyEZDbKIkMxdQunsIhpUMes= +go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= +go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/log v0.16.0 h1:e/b4bdlQwC5fnGtG3dlXUrNOnP7c8YLVSpSfEBIkTnI= +go.opentelemetry.io/otel/sdk/log v0.16.0/go.mod h1:JKfP3T6ycy7QEuv3Hj8oKDy7KItrEkus8XJE6EoSzw4= +go.opentelemetry.io/otel/sdk/log/logtest v0.16.0 h1:/XVkpZ41rVRTP4DfMgYv1nEtNmf65XPPyAdqV90TMy4= +go.opentelemetry.io/otel/sdk/log/logtest v0.16.0/go.mod h1:iOOPgQr5MY9oac/F5W86mXdeyWZGleIx3uXO98X2R6Y= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= +go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -397,8 +401,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -457,14 +461,16 @@ golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= -google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From 16573f87f5aebf8c2f9c40e67cc3cbe5eb93e733 Mon Sep 17 00:00:00 2001 From: Travis Leeden Date: Wed, 4 Mar 2026 15:53:35 +1000 Subject: [PATCH 210/571] Restored --atomic flag on install command Signed-off-by: Travis Leeden --- pkg/cmd/install.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index d36cd9e34..ed10513c9 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -201,6 +201,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart") f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema") f.BoolVar(&client.RollbackOnFailure, "rollback-on-failure", false, "if set, Helm will rollback (uninstall) the installation upon failure. The --wait flag will be default to \"watcher\" if --rollback-on-failure is set") + f.BoolVar(&client.RollbackOnFailure, "atomic", false, "deprecated") f.MarkDeprecated("atomic", "use --rollback-on-failure instead") f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") From 0737e438aae04fca872b66f185b0f2aab8bd0d86 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:49:05 +0100 Subject: [PATCH 211/571] Update pkg/cmd/history_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/cmd/history_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/cmd/history_test.go b/pkg/cmd/history_test.go index 1b07b1679..e76a1f63d 100644 --- a/pkg/cmd/history_test.go +++ b/pkg/cmd/history_test.go @@ -27,7 +27,6 @@ import ( "helm.sh/helm/v4/pkg/release/common" release "helm.sh/helm/v4/pkg/release/v1" - chart "helm.sh/helm/v4/pkg/chart/v2" ) From 681ccb19a68dab98b75e407ba38b37c45ee9c5d1 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:45:55 +0100 Subject: [PATCH 212/571] Update pkg/cmd/testdata/output/history-with-rollback.txt Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/cmd/testdata/output/history-with-rollback.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/cmd/testdata/output/history-with-rollback.txt b/pkg/cmd/testdata/output/history-with-rollback.txt index c6dd62b8a..4fcb957dc 100644 --- a/pkg/cmd/testdata/output/history-with-rollback.txt +++ b/pkg/cmd/testdata/output/history-with-rollback.txt @@ -1,4 +1,4 @@ -REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION -1 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Install complete -2 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Upgrade complete -3 Fri Sep 2 22:04:05 1977 deployed foo-0.1.0-beta.1 1.0 Rollback to 1 +REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION ROLLBACK +1 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Install complete 0 +2 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Upgrade complete 0 +3 Fri Sep 2 22:04:05 1977 deployed foo-0.1.0-beta.1 1.0 Rollback to 1 1 From e31a078e6e0667dde72ff3bf4b5dfb625127076f Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 6 Mar 2026 15:54:33 +0100 Subject: [PATCH 213/571] chore: enable modernize linter (#31860) Signed-off-by: Matthieu MOREL --- .github/env | 2 +- .golangci.yml | 29 ++++++++++++++++++++--------- pkg/downloader/chart_downloader.go | 4 ++-- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/.github/env b/.github/env index 9d79b174f..a22fa80ca 100644 --- a/.github/env +++ b/.github/env @@ -1,2 +1,2 @@ GOLANG_VERSION=1.25 -GOLANGCI_LINT_VERSION=v2.5.0 +GOLANGCI_LINT_VERSION=v2.10.1 diff --git a/.golangci.yml b/.golangci.yml index d808421a0..536b4b212 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -17,13 +17,16 @@ formatters: linters: default: none + # Keep sorted alphabetically enable: - depguard - dupl + - exhaustive - gomodguard - govet - ineffassign - misspell + - modernize - nakedret - nolintlint - revive @@ -33,7 +36,6 @@ linters: - unused - usestdlibvars - usetesting - - exhaustive exclusions: @@ -45,16 +47,9 @@ linters: - legacy - std-error-handling - rules: - # This rule is triggered for packages like 'util'. When changes to those packages - # occur it triggers this rule. This exclusion enables making changes to existing - # packages. - - linters: - - revive - text: 'var-naming: avoid meaningless package names' - warn-unused: true + # Keep sorted alphabetically settings: depguard: rules: @@ -81,6 +76,22 @@ linters: nolintlint: require-specific: true + revive: + max-open-files: 2048 + enable-default-rules: true + rules: + - name: var-naming + arguments: + - ["ID"] # AllowList + - ["VM"] # DenyList + - - skip-initialism-name-checks: true + upper-case-const: true + skip-package-name-checks: true + skip-package-name-collision-with-go-std: true + extra-bad-package-names: + - helpers + - models + run: timeout: 10m diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index d21a79335..9c26f925e 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -597,8 +597,8 @@ func loadRepoConfig(file string) (*repo.File, error) { // stripDigestAlgorithm removes the algorithm prefix (e.g., "sha256:") from a digest string. // If no prefix is present, the original string is returned unchanged. func stripDigestAlgorithm(digest string) string { - if idx := strings.Index(digest, ":"); idx >= 0 { - return digest[idx+1:] + if _, after, ok := strings.Cut(digest, ":"); ok { + return after } return digest } From e889cff0896d842c7e1854729611ac520a925d0b Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Mon, 9 Mar 2026 09:45:04 +0100 Subject: [PATCH 214/571] feat(history): add --show-rollback flag for opt-in rollback column Replace the always-visible ROLLBACK column with an opt-in --show-rollback flag to avoid breaking the default table output (HIP-0004). JSON and YAML formats continue to include rollback_revision when present via omitempty. Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/cmd/history.go | 34 +++++++++++++++++++ pkg/cmd/history_test.go | 9 +++-- pkg/cmd/testdata/output/history-limit.txt | 6 ++-- .../output/history-with-rollback-no-flag.txt | 4 +++ .../testdata/output/history-with-rollback.txt | 8 ++--- pkg/cmd/testdata/output/history.txt | 10 +++--- 6 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 pkg/cmd/testdata/output/history-with-rollback-no-flag.txt diff --git a/pkg/cmd/history.go b/pkg/cmd/history.go index a040cd16a..eb1c88d66 100644 --- a/pkg/cmd/history.go +++ b/pkg/cmd/history.go @@ -43,6 +43,15 @@ configures the maximum length of the revision list returned. The historical release set is printed as a formatted table, e.g: $ helm history angry-bird + REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION + 1 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Initial install + 2 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Upgraded successfully + 3 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Rolled back to 2 + 4 Mon Oct 3 10:15:13 2016 deployed alpine-0.1.0 1.0 Upgraded successfully + +Use '--show-rollback' to include a column showing the revision that was rolled back to: + + $ helm history angry-bird --show-rollback REVISION UPDATED STATUS CHART APP VERSION ROLLBACK DESCRIPTION 1 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Initial install 2 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Upgraded successfully @@ -53,6 +62,7 @@ The historical release set is printed as a formatted table, e.g: func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewHistory(cfg) var outfmt output.Format + var showRollback bool cmd := &cobra.Command{ Use: "history RELEASE_NAME", @@ -72,12 +82,16 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return err } + if showRollback { + return outfmt.Write(out, releaseHistoryWithRollback(history)) + } return outfmt.Write(out, history) }, } f := cmd.Flags() f.IntVar(&client.Max, "max", 256, "maximum number of revision to include in history") + f.BoolVar(&showRollback, "show-rollback", false, "show the rollback revision column in the output") bindOutputFlag(cmd, &outfmt) return cmd @@ -176,6 +190,26 @@ func (r releaseHistory) WriteYAML(out io.Writer) error { } func (r releaseHistory) WriteTable(out io.Writer) error { + tbl := uitable.New() + tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "DESCRIPTION") + for _, item := range r { + tbl.AddRow(item.Revision, item.Updated.Format(time.ANSIC), item.Status, item.Chart, item.AppVersion, item.Description) + } + return output.EncodeTable(out, tbl) +} + +// releaseHistoryWithRollback wraps releaseHistory to include the rollback column in table output. +type releaseHistoryWithRollback releaseHistory + +func (r releaseHistoryWithRollback) WriteJSON(out io.Writer) error { + return output.EncodeJSON(out, releaseHistory(r)) +} + +func (r releaseHistoryWithRollback) WriteYAML(out io.Writer) error { + return output.EncodeYAML(out, releaseHistory(r)) +} + +func (r releaseHistoryWithRollback) WriteTable(out io.Writer) error { tbl := uitable.New() tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "ROLLBACK", "DESCRIPTION") for _, item := range r { diff --git a/pkg/cmd/history_test.go b/pkg/cmd/history_test.go index e76a1f63d..b25eaba7f 100644 --- a/pkg/cmd/history_test.go +++ b/pkg/cmd/history_test.go @@ -25,9 +25,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/release/common" release "helm.sh/helm/v4/pkg/release/v1" - chart "helm.sh/helm/v4/pkg/chart/v2" ) func TestHistoryCmd(t *testing.T) { @@ -125,9 +125,14 @@ func TestHistoryWithRollback(t *testing.T) { } tests := []cmdTestCase{{ - name: "history with rollback revision", + name: "history with rollback revision (default, no rollback column)", cmd: "history angry-bird", rels: rels, + golden: "output/history-with-rollback-no-flag.txt", + }, { + name: "history with rollback revision and --show-rollback flag", + cmd: "history angry-bird --show-rollback", + rels: rels, golden: "output/history-with-rollback.txt", }, { name: "history with rollback revision json", diff --git a/pkg/cmd/testdata/output/history-limit.txt b/pkg/cmd/testdata/output/history-limit.txt index f6f059fb9..aee0fadb2 100644 --- a/pkg/cmd/testdata/output/history-limit.txt +++ b/pkg/cmd/testdata/output/history-limit.txt @@ -1,3 +1,3 @@ -REVISION UPDATED STATUS CHART APP VERSION ROLLBACK DESCRIPTION -3 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock -4 Fri Sep 2 22:04:05 1977 deployed foo-0.1.0-beta.1 1.0 Release mock +REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION +3 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock +4 Fri Sep 2 22:04:05 1977 deployed foo-0.1.0-beta.1 1.0 Release mock diff --git a/pkg/cmd/testdata/output/history-with-rollback-no-flag.txt b/pkg/cmd/testdata/output/history-with-rollback-no-flag.txt new file mode 100644 index 000000000..8861b7572 --- /dev/null +++ b/pkg/cmd/testdata/output/history-with-rollback-no-flag.txt @@ -0,0 +1,4 @@ +REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION +1 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Install complete +2 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Upgrade complete +3 Fri Sep 2 22:04:05 1977 deployed foo-0.1.0-beta.1 1.0 Rollback to 1 diff --git a/pkg/cmd/testdata/output/history-with-rollback.txt b/pkg/cmd/testdata/output/history-with-rollback.txt index 4fcb957dc..26dd210af 100644 --- a/pkg/cmd/testdata/output/history-with-rollback.txt +++ b/pkg/cmd/testdata/output/history-with-rollback.txt @@ -1,4 +1,4 @@ -REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION ROLLBACK -1 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Install complete 0 -2 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Upgrade complete 0 -3 Fri Sep 2 22:04:05 1977 deployed foo-0.1.0-beta.1 1.0 Rollback to 1 1 +REVISION UPDATED STATUS CHART APP VERSION ROLLBACK DESCRIPTION +1 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Install complete +2 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Upgrade complete +3 Fri Sep 2 22:04:05 1977 deployed foo-0.1.0-beta.1 1.0 1 Rollback to 1 diff --git a/pkg/cmd/testdata/output/history.txt b/pkg/cmd/testdata/output/history.txt index 4a51add7f..2a5d69c11 100644 --- a/pkg/cmd/testdata/output/history.txt +++ b/pkg/cmd/testdata/output/history.txt @@ -1,5 +1,5 @@ -REVISION UPDATED STATUS CHART APP VERSION ROLLBACK DESCRIPTION -1 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock -2 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock -3 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock -4 Fri Sep 2 22:04:05 1977 deployed foo-0.1.0-beta.1 1.0 Release mock +REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION +1 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock +2 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock +3 Fri Sep 2 22:04:05 1977 superseded foo-0.1.0-beta.1 1.0 Release mock +4 Fri Sep 2 22:04:05 1977 deployed foo-0.1.0-beta.1 1.0 Release mock From 6927cde3f98d9e5f248418805e40c7408e629746 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:43:27 +0100 Subject: [PATCH 215/571] fix: address Copilot review feedback on rollback revision PR - Clarify --show-rollback flag help text to specify it only affects table output - Add RollbackRevision JSON round-trip tests for pkg/release/v1 and internal/release/v2 - Add omitempty behavior verification for zero rollback_revision Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- internal/release/v2/info_test.go | 98 ++++++++++++++++++++++++++++++++ pkg/cmd/history.go | 2 +- pkg/release/v1/info_test.go | 98 ++++++++++++++++++++++++++++++++ 3 files changed, 197 insertions(+), 1 deletion(-) diff --git a/internal/release/v2/info_test.go b/internal/release/v2/info_test.go index 560861e06..5812c6560 100644 --- a/internal/release/v2/info_test.go +++ b/internal/release/v2/info_test.go @@ -87,6 +87,27 @@ func TestInfoMarshalJSON(t *testing.T) { }, expected: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","deleted":"2025-10-08T14:00:00Z","description":"Uninstalled release","status":"uninstalled"}`, }, + { + name: "with rollback revision", + info: Info{ + FirstDeployed: now, + LastDeployed: later, + Status: common.StatusDeployed, + RollbackRevision: 2, + Description: "Rollback to 2", + }, + expected: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","status":"deployed","rollback_revision":2,"description":"Rollback to 2"}`, + }, + { + name: "zero rollback revision omitted", + info: Info{ + FirstDeployed: now, + LastDeployed: later, + Status: common.StatusDeployed, + Description: "Normal install", + }, + expected: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","status":"deployed","description":"Normal install"}`, + }, } for _, tt := range tests { @@ -203,6 +224,27 @@ func TestInfoUnmarshalJSON(t *testing.T) { Status: "", }, }, + { + name: "with rollback revision", + input: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","status":"deployed","rollback_revision":2,"description":"Rollback to 2"}`, + expected: Info{ + FirstDeployed: now, + LastDeployed: later, + Status: common.StatusDeployed, + RollbackRevision: 2, + Description: "Rollback to 2", + }, + }, + { + name: "zero rollback revision omitted", + input: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","status":"deployed","description":"Normal install"}`, + expected: Info{ + FirstDeployed: now, + LastDeployed: later, + Status: common.StatusDeployed, + Description: "Normal install", + }, + }, } for _, tt := range tests { @@ -219,6 +261,7 @@ func TestInfoUnmarshalJSON(t *testing.T) { assert.Equal(t, tt.expected.Deleted.Unix(), info.Deleted.Unix()) assert.Equal(t, tt.expected.Description, info.Description) assert.Equal(t, tt.expected.Status, info.Status) + assert.Equal(t, tt.expected.RollbackRevision, info.RollbackRevision) assert.Equal(t, tt.expected.Notes, info.Notes) assert.Equal(t, tt.expected.Resources, info.Resources) }) @@ -252,6 +295,61 @@ func TestInfoRoundTrip(t *testing.T) { assert.Equal(t, original.Notes, decoded.Notes) } +func TestInfoRollbackRevisionRoundTrip(t *testing.T) { + now := time.Date(2025, 10, 8, 12, 0, 0, 0, time.UTC) + later := time.Date(2025, 10, 8, 13, 0, 0, 0, time.UTC) + + tests := []struct { + name string + info Info + }{ + { + name: "with rollback revision", + info: Info{ + FirstDeployed: now, + LastDeployed: later, + Description: "Rollback to 2", + Status: common.StatusDeployed, + RollbackRevision: 2, + }, + }, + { + name: "zero rollback revision", + info: Info{ + FirstDeployed: now, + LastDeployed: later, + Description: "Normal install", + Status: common.StatusDeployed, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := json.Marshal(&tt.info) + require.NoError(t, err) + + var decoded Info + err = json.Unmarshal(data, &decoded) + require.NoError(t, err) + + assert.Equal(t, tt.info.RollbackRevision, decoded.RollbackRevision) + assert.Equal(t, tt.info.FirstDeployed.Unix(), decoded.FirstDeployed.Unix()) + assert.Equal(t, tt.info.LastDeployed.Unix(), decoded.LastDeployed.Unix()) + assert.Equal(t, tt.info.Status, decoded.Status) + assert.Equal(t, tt.info.Description, decoded.Description) + + // Verify omitempty behavior: zero rollback_revision should not appear in JSON + if tt.info.RollbackRevision == 0 { + var raw map[string]any + err = json.Unmarshal(data, &raw) + require.NoError(t, err) + assert.NotContains(t, raw, "rollback_revision") + } + }) + } +} + func TestInfoEmptyStringRoundTrip(t *testing.T) { // This test specifically verifies that empty string time fields // are handled correctly during parsing diff --git a/pkg/cmd/history.go b/pkg/cmd/history.go index eb1c88d66..49de85a06 100644 --- a/pkg/cmd/history.go +++ b/pkg/cmd/history.go @@ -91,7 +91,7 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f := cmd.Flags() f.IntVar(&client.Max, "max", 256, "maximum number of revision to include in history") - f.BoolVar(&showRollback, "show-rollback", false, "show the rollback revision column in the output") + f.BoolVar(&showRollback, "show-rollback", false, "show the rollback revision column in table output") bindOutputFlag(cmd, &outfmt) return cmd diff --git a/pkg/release/v1/info_test.go b/pkg/release/v1/info_test.go index 6cff4db64..12b3014f6 100644 --- a/pkg/release/v1/info_test.go +++ b/pkg/release/v1/info_test.go @@ -87,6 +87,27 @@ func TestInfoMarshalJSON(t *testing.T) { }, expected: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","deleted":"2025-10-08T14:00:00Z","description":"Uninstalled release","status":"uninstalled"}`, }, + { + name: "with rollback revision", + info: Info{ + FirstDeployed: now, + LastDeployed: later, + Status: common.StatusDeployed, + RollbackRevision: 2, + Description: "Rollback to 2", + }, + expected: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","status":"deployed","rollback_revision":2,"description":"Rollback to 2"}`, + }, + { + name: "zero rollback revision omitted", + info: Info{ + FirstDeployed: now, + LastDeployed: later, + Status: common.StatusDeployed, + Description: "Normal install", + }, + expected: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","status":"deployed","description":"Normal install"}`, + }, } for _, tt := range tests { @@ -203,6 +224,27 @@ func TestInfoUnmarshalJSON(t *testing.T) { Status: "", }, }, + { + name: "with rollback revision", + input: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","status":"deployed","rollback_revision":2,"description":"Rollback to 2"}`, + expected: Info{ + FirstDeployed: now, + LastDeployed: later, + Status: common.StatusDeployed, + RollbackRevision: 2, + Description: "Rollback to 2", + }, + }, + { + name: "zero rollback revision omitted", + input: `{"first_deployed":"2025-10-08T12:00:00Z","last_deployed":"2025-10-08T13:00:00Z","status":"deployed","description":"Normal install"}`, + expected: Info{ + FirstDeployed: now, + LastDeployed: later, + Status: common.StatusDeployed, + Description: "Normal install", + }, + }, } for _, tt := range tests { @@ -219,6 +261,7 @@ func TestInfoUnmarshalJSON(t *testing.T) { assert.Equal(t, tt.expected.Deleted.Unix(), info.Deleted.Unix()) assert.Equal(t, tt.expected.Description, info.Description) assert.Equal(t, tt.expected.Status, info.Status) + assert.Equal(t, tt.expected.RollbackRevision, info.RollbackRevision) assert.Equal(t, tt.expected.Notes, info.Notes) assert.Equal(t, tt.expected.Resources, info.Resources) }) @@ -252,6 +295,61 @@ func TestInfoRoundTrip(t *testing.T) { assert.Equal(t, original.Notes, decoded.Notes) } +func TestInfoRollbackRevisionRoundTrip(t *testing.T) { + now := time.Date(2025, 10, 8, 12, 0, 0, 0, time.UTC) + later := time.Date(2025, 10, 8, 13, 0, 0, 0, time.UTC) + + tests := []struct { + name string + info Info + }{ + { + name: "with rollback revision", + info: Info{ + FirstDeployed: now, + LastDeployed: later, + Description: "Rollback to 2", + Status: common.StatusDeployed, + RollbackRevision: 2, + }, + }, + { + name: "zero rollback revision", + info: Info{ + FirstDeployed: now, + LastDeployed: later, + Description: "Normal install", + Status: common.StatusDeployed, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := json.Marshal(&tt.info) + require.NoError(t, err) + + var decoded Info + err = json.Unmarshal(data, &decoded) + require.NoError(t, err) + + assert.Equal(t, tt.info.RollbackRevision, decoded.RollbackRevision) + assert.Equal(t, tt.info.FirstDeployed.Unix(), decoded.FirstDeployed.Unix()) + assert.Equal(t, tt.info.LastDeployed.Unix(), decoded.LastDeployed.Unix()) + assert.Equal(t, tt.info.Status, decoded.Status) + assert.Equal(t, tt.info.Description, decoded.Description) + + // Verify omitempty behavior: zero rollback_revision should not appear in JSON + if tt.info.RollbackRevision == 0 { + var raw map[string]any + err = json.Unmarshal(data, &raw) + require.NoError(t, err) + assert.NotContains(t, raw, "rollback_revision") + } + }) + } +} + func TestInfoEmptyStringRoundTrip(t *testing.T) { // This test specifically verifies that empty string time fields // are handled correctly during parsing From 16073b1e3c7b90cda41ed70c8192eb6d16816723 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 17:05:07 +0000 Subject: [PATCH 216/571] chore(deps): bump github.com/cloudflare/circl from 1.6.1 to 1.6.3 Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.6.1 to 1.6.3. - [Release notes](https://github.com/cloudflare/circl/releases) - [Commits](https://github.com/cloudflare/circl/compare/v1.6.1...v1.6.3) --- updated-dependencies: - dependency-name: github.com/cloudflare/circl dependency-version: 1.6.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 814cbc17b..34f102be2 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,7 @@ require ( github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/cloudflare/circl v1.6.1 // indirect + github.com/cloudflare/circl v1.6.3 // 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 243cb6f85..75465660b 100644 --- a/go.sum +++ b/go.sum @@ -49,8 +49,8 @@ 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/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= 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= From af94abf976ce69dd635aaf086a0bb4b17bd95bc1 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Thu, 26 Feb 2026 15:04:07 +0000 Subject: [PATCH 217/571] fix: insert newline after doc separators glued to content by template trimming Signed-off-by: Matheus Pimenta --- pkg/action/action.go | 40 ++++++++++ pkg/action/action_test.go | 149 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) diff --git a/pkg/action/action.go b/pkg/action/action.go index 49d7316c0..75b6cf8a0 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -144,6 +144,39 @@ const ( filenameAnnotation = "postrenderer.helm.sh/postrender-filename" ) +// fixDocSeparators ensures YAML document separators ("---") are always +// followed by a newline in rendered template content. Go template whitespace +// trimming ({{-) can remove the newline after "---", producing e.g. +// "---apiVersion: v1" which is not a valid YAML document separator. +// This function inserts a newline after any "---" at the start of a line +// that is immediately followed by non-whitespace content. +func fixDocSeparators(content string) string { + var b strings.Builder + remaining := content + for { + // Find "---" at the start of a line (or start of content). + idx := strings.Index(remaining, "---") + if idx == -1 { + b.WriteString(remaining) + break + } + // "---" must be at the start of a line: either idx==0 or preceded by '\n'. + if idx > 0 && remaining[idx-1] != '\n' { + b.WriteString(remaining[:idx+3]) + remaining = remaining[idx+3:] + continue + } + b.WriteString(remaining[:idx+3]) + remaining = remaining[idx+3:] + // If "---" is followed by non-whitespace (e.g. "---apiVersion"), + // insert a newline to make it a proper document separator. + if len(remaining) > 0 && remaining[0] != '\n' && remaining[0] != '\r' && remaining[0] != ' ' && remaining[0] != '\t' { + b.WriteByte('\n') + } + } + return b.String() +} + // annotateAndMerge combines multiple YAML files into a single stream of documents, // adding filename annotations to each document for later reconstruction. func annotateAndMerge(files map[string]string) (string, error) { @@ -159,6 +192,13 @@ func annotateAndMerge(files map[string]string) (string, error) { continue } + // Fix document separators where Go template whitespace trimming + // ({{-) has removed the newline after "---", producing e.g. + // "---apiVersion: v1" which is not a valid YAML document + // separator. Insert the missing newline so kio.ParseAll can + // parse the content correctly. + content = fixDocSeparators(content) + manifests, err := kio.ParseAll(content) if err != nil { return "", fmt.Errorf("parsing %s: %w", fname, err) diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index 29a4885bd..5271e34fa 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -403,6 +403,96 @@ func (m *mockPostRenderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, return bytes.NewBufferString(content), nil } +func TestFixDocSeparators(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "no separator", + input: "apiVersion: v1\nkind: Service\n", + expected: "apiVersion: v1\nkind: Service\n", + }, + { + name: "separator on its own line", + input: "---\napiVersion: v1\nkind: Service\n", + expected: "---\napiVersion: v1\nkind: Service\n", + }, + { + name: "leading separator glued to content", + input: "---apiVersion: v1\nkind: Service\n", + expected: "---\napiVersion: v1\nkind: Service\n", + }, + { + name: "mid-content separator glued to content", + input: "apiVersion: v1\nkind: ConfigMap\n---apiVersion: v1\nkind: Service\n", + expected: "apiVersion: v1\nkind: ConfigMap\n---\napiVersion: v1\nkind: Service\n", + }, + { + name: "multiple separators all proper", + input: "---\napiVersion: v1\n---\napiVersion: v1\n", + expected: "---\napiVersion: v1\n---\napiVersion: v1\n", + }, + { + name: "multiple separators some glued", + input: "---apiVersion: v1\nkind: ConfigMap\n---apiVersion: v1\nkind: Service\n", + expected: "---\napiVersion: v1\nkind: ConfigMap\n---\napiVersion: v1\nkind: Service\n", + }, + { + name: "empty string", + input: "", + expected: "", + }, + { + name: "only separator", + input: "---\n", + expected: "---\n", + }, + { + name: "triple dash in a value is not a separator", + input: "data:\n key: ---value\n", + expected: "data:\n key: ---value\n", + }, + { + name: "realistic multi-doc template output", + input: "apiVersion: v1\nkind: Deployment\n---\napiVersion: v1\nkind: Ingress\n---apiVersion: v1\nkind: Service\n", + expected: "apiVersion: v1\nkind: Deployment\n---\napiVersion: v1\nkind: Ingress\n---\napiVersion: v1\nkind: Service\n", + }, + { + name: "separator followed by carriage return", + input: "---\r\napiVersion: v1\n", + expected: "---\r\napiVersion: v1\n", + }, + { + name: "separator followed by space", + input: "--- \napiVersion: v1\n", + expected: "--- \napiVersion: v1\n", + }, + { + name: "separator followed by tab", + input: "---\t\napiVersion: v1\n", + expected: "---\t\napiVersion: v1\n", + }, + { + name: "four dashes on its own line", + input: "----\napiVersion: v1\n", + expected: "---\n-\napiVersion: v1\n", + }, + { + name: "four dashes followed by text", + input: "----more\napiVersion: v1\n", + expected: "---\n-more\napiVersion: v1\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, fixDocSeparators(tt.input)) + }) + } +} + func TestAnnotateAndMerge(t *testing.T) { tests := []struct { name string @@ -543,6 +633,65 @@ metadata: }, expectedError: "parsing templates/invalid.yaml", }, + { + name: "leading doc separator glued to content by template whitespace trimming", + files: map[string]string{ + "templates/service.yaml": "---apiVersion: v1\nkind: Service\nmetadata:\n name: test-svc\n", + }, + expected: `apiVersion: v1 +kind: Service +metadata: + name: test-svc + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/service.yaml' +`, + }, + { + name: "leading doc separator on its own line", + files: map[string]string{ + "templates/service.yaml": "---\napiVersion: v1\nkind: Service\nmetadata:\n name: test-svc\n", + }, + expected: `apiVersion: v1 +kind: Service +metadata: + name: test-svc + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/service.yaml' +`, + }, + { + name: "multiple leading doc separators", + files: map[string]string{ + "templates/service.yaml": "---\n---\napiVersion: v1\nkind: Service\nmetadata:\n name: test-svc\n", + }, + expected: `apiVersion: v1 +kind: Service +metadata: + name: test-svc + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/service.yaml' +`, + }, + { + name: "mid-content doc separator glued to content by template whitespace trimming", + files: map[string]string{ + "templates/all.yaml": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test-cm\n---apiVersion: v1\nkind: Service\nmetadata:\n name: test-svc\n", + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test-cm + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/all.yaml' +--- +apiVersion: v1 +kind: Service +metadata: + name: test-svc + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/all.yaml' +`, + }, } for _, tt := range tests { From 74e7cf877a4a674b65f7b7894d2dfde2832e39b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:50:04 +0000 Subject: [PATCH 218/571] chore(deps): bump sigs.k8s.io/controller-runtime from 0.23.1 to 0.23.3 Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.23.1 to 0.23.3. - [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.23.1...v0.23.3) --- updated-dependencies: - dependency-name: sigs.k8s.io/controller-runtime dependency-version: 0.23.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 34f102be2..65344ea63 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( k8s.io/klog/v2 v2.130.1 k8s.io/kubectl v0.35.1 oras.land/oras-go/v2 v2.6.0 - sigs.k8s.io/controller-runtime v0.23.1 + sigs.k8s.io/controller-runtime v0.23.3 sigs.k8s.io/kustomize/kyaml v0.21.1 sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index 75465660b..04bfe426f 100644 --- a/go.sum +++ b/go.sum @@ -509,8 +509,8 @@ k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzk k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= -sigs.k8s.io/controller-runtime v0.23.1 h1:TjJSM80Nf43Mg21+RCy3J70aj/W6KyvDtOlpKf+PupE= -sigs.k8s.io/controller-runtime v0.23.1/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= +sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= +sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= From 60184996e5332d26e0b6390cefbf86776829dc46 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Fri, 6 Mar 2026 08:01:01 -0800 Subject: [PATCH 219/571] fix: Chart dot-name path bug Signed-off-by: George Jenkins --- internal/chart/v3/metadata.go | 3 + internal/chart/v3/metadata_test.go | 10 +++ internal/chart/v3/util/expand.go | 17 ++++ internal/chart/v3/util/expand_test.go | 84 +++++++++++++++++++ .../v3/util/testdata/dotdotname/Chart.yaml | 4 + .../chart/v3/util/testdata/dotname/Chart.yaml | 4 + .../v3/util/testdata/slashinname/Chart.yaml | 4 + pkg/chart/v2/metadata.go | 3 + pkg/chart/v2/metadata_test.go | 10 +++ pkg/chart/v2/util/expand.go | 17 ++++ pkg/chart/v2/util/expand_test.go | 84 +++++++++++++++++++ .../v2/util/testdata/dotdotname/Chart.yaml | 4 + pkg/chart/v2/util/testdata/dotname/Chart.yaml | 4 + .../v2/util/testdata/slashinname/Chart.yaml | 4 + 14 files changed, 252 insertions(+) create mode 100644 internal/chart/v3/util/testdata/dotdotname/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dotname/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/slashinname/Chart.yaml create mode 100644 pkg/chart/v2/util/testdata/dotdotname/Chart.yaml create mode 100644 pkg/chart/v2/util/testdata/dotname/Chart.yaml create mode 100644 pkg/chart/v2/util/testdata/slashinname/Chart.yaml diff --git a/internal/chart/v3/metadata.go b/internal/chart/v3/metadata.go index 4629d571b..5f7cea897 100644 --- a/internal/chart/v3/metadata.go +++ b/internal/chart/v3/metadata.go @@ -112,6 +112,9 @@ func (md *Metadata) Validate() error { return ValidationError("chart.metadata.name is required") } + if md.Name == "." || md.Name == ".." { + return ValidationErrorf("chart.metadata.name %q is not allowed", md.Name) + } if md.Name != filepath.Base(md.Name) { return ValidationErrorf("chart.metadata.name %q is invalid", md.Name) } diff --git a/internal/chart/v3/metadata_test.go b/internal/chart/v3/metadata_test.go index 3d773e7f4..5f88552e9 100644 --- a/internal/chart/v3/metadata_test.go +++ b/internal/chart/v3/metadata_test.go @@ -41,6 +41,16 @@ func TestValidate(t *testing.T) { &Metadata{APIVersion: "v3", Version: "1.0"}, ValidationError("chart.metadata.name is required"), }, + { + "chart with dot name", + &Metadata{Name: ".", APIVersion: "v3", Version: "1.0"}, + ValidationError("chart.metadata.name \".\" is not allowed"), + }, + { + "chart with dotdot name", + &Metadata{Name: "..", APIVersion: "v3", Version: "1.0"}, + ValidationError("chart.metadata.name \"..\" is not allowed"), + }, { "chart without name", &Metadata{Name: "../../test", APIVersion: "v3", Version: "1.0"}, diff --git a/internal/chart/v3/util/expand.go b/internal/chart/v3/util/expand.go index 1a10fce3c..5b057b8e0 100644 --- a/internal/chart/v3/util/expand.go +++ b/internal/chart/v3/util/expand.go @@ -52,6 +52,17 @@ func Expand(dir string, r io.Reader) error { return errors.New("chart name not specified") } + // Reject chart names that are POSIX path dot-segments or dot-dot segments or contain path separators. + // A dot-segment name (e.g. ".") causes SecureJoin to resolve to the root + // directory and extraction then to write files directly into that extraction root + // instead of a per-chart subdirectory. + if chartName == "." || chartName == ".." { + return fmt.Errorf("chart name %q is not allowed", chartName) + } + if chartName != filepath.Base(chartName) { + return fmt.Errorf("chart name %q must not contain path separators", chartName) + } + // Find the base directory // The directory needs to be cleaned prior to passing to SecureJoin or the location may end up // being wrong or returning an error. This was introduced in v0.4.0. @@ -61,6 +72,12 @@ func Expand(dir string, r io.Reader) error { return err } + // Defense-in-depth: the chart directory must be a subdirectory of dir, + // never dir itself. + if chartdir == dir { + return fmt.Errorf("chart name %q resolves to the extraction root", chartName) + } + // Copy all files verbatim. We don't parse these files because parsing can remove // comments. for _, file := range files { diff --git a/internal/chart/v3/util/expand_test.go b/internal/chart/v3/util/expand_test.go index 280995f7e..e9e298b81 100644 --- a/internal/chart/v3/util/expand_test.go +++ b/internal/chart/v3/util/expand_test.go @@ -17,11 +17,73 @@ limitations under the License. package util import ( + "archive/tar" + "bytes" + "compress/gzip" + "io/fs" "os" "path/filepath" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +// makeTestChartArchive builds a gzipped tar archive from the given sourceDir directory, file entries are prefixed with the given chartName +func makeTestChartArchive(t *testing.T, chartName, sourceDir string) *bytes.Buffer { + t.Helper() + + var result bytes.Buffer + gw := gzip.NewWriter(&result) + tw := tar.NewWriter(gw) + + dir := os.DirFS(sourceDir) + + writeFile := func(relPath string) { + t.Helper() + f, err := dir.Open(relPath) + require.NoError(t, err) + + fStat, err := f.Stat() + require.NoError(t, err) + + err = tw.WriteHeader(&tar.Header{ + Name: filepath.Join(chartName, relPath), + Mode: int64(fStat.Mode()), + Size: fStat.Size(), + }) + require.NoError(t, err) + + data, err := fs.ReadFile(dir, relPath) + require.NoError(t, err) + tw.Write(data) + } + + err := fs.WalkDir(dir, ".", func(path string, d os.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + + if d.IsDir() { + return nil + } + + writeFile(path) + + return nil + }) + if err != nil { + t.Fatal(err) + } + + err = tw.Close() + require.NoError(t, err) + err = gw.Close() + require.NoError(t, err) + + return &result +} + func TestExpand(t *testing.T) { dest := t.TempDir() @@ -75,6 +137,28 @@ func TestExpand(t *testing.T) { } } +func TestExpandError(t *testing.T) { + tests := map[string]struct { + chartName string + chartDir string + wantErr string + }{ + "dot name": {"dotname", "testdata/dotname", "not allowed"}, + "dotdot name": {"dotdotname", "testdata/dotdotname", "not allowed"}, + "slash in name": {"slashinname", "testdata/slashinname", "must not contain path separators"}, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + archive := makeTestChartArchive(t, tt.chartName, tt.chartDir) + + dest := t.TempDir() + err := Expand(dest, archive) + assert.ErrorContains(t, err, tt.wantErr) + }) + } +} + func TestExpandFile(t *testing.T) { dest := t.TempDir() diff --git a/internal/chart/v3/util/testdata/dotdotname/Chart.yaml b/internal/chart/v3/util/testdata/dotdotname/Chart.yaml new file mode 100644 index 000000000..9b081f27b --- /dev/null +++ b/internal/chart/v3/util/testdata/dotdotname/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v3 +name: .. +description: A Helm chart for Kubernetes +version: 0.1.0 \ No newline at end of file diff --git a/internal/chart/v3/util/testdata/dotname/Chart.yaml b/internal/chart/v3/util/testdata/dotname/Chart.yaml new file mode 100644 index 000000000..597c16290 --- /dev/null +++ b/internal/chart/v3/util/testdata/dotname/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v3 +name: . +description: A Helm chart for Kubernetes +version: 0.1.0 \ No newline at end of file diff --git a/internal/chart/v3/util/testdata/slashinname/Chart.yaml b/internal/chart/v3/util/testdata/slashinname/Chart.yaml new file mode 100644 index 000000000..0c522a4b6 --- /dev/null +++ b/internal/chart/v3/util/testdata/slashinname/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v3 +name: a/../b +description: A Helm chart for Kubernetes +version: 0.1.0 \ No newline at end of file diff --git a/pkg/chart/v2/metadata.go b/pkg/chart/v2/metadata.go index c46007863..5df307321 100644 --- a/pkg/chart/v2/metadata.go +++ b/pkg/chart/v2/metadata.go @@ -112,6 +112,9 @@ func (md *Metadata) Validate() error { return ValidationError("chart.metadata.name is required") } + if md.Name == "." || md.Name == ".." { + return ValidationErrorf("chart.metadata.name %q is not allowed", md.Name) + } if md.Name != filepath.Base(md.Name) { return ValidationErrorf("chart.metadata.name %q is invalid", md.Name) } diff --git a/pkg/chart/v2/metadata_test.go b/pkg/chart/v2/metadata_test.go index 2f092f6d2..63dd99e52 100644 --- a/pkg/chart/v2/metadata_test.go +++ b/pkg/chart/v2/metadata_test.go @@ -41,6 +41,16 @@ func TestValidate(t *testing.T) { &Metadata{APIVersion: "v2", Version: "1.0"}, ValidationError("chart.metadata.name is required"), }, + { + "chart with dot name", + &Metadata{Name: ".", APIVersion: "v2", Version: "1.0"}, + ValidationError("chart.metadata.name \".\" is not allowed"), + }, + { + "chart with dotdot name", + &Metadata{Name: "..", APIVersion: "v2", Version: "1.0"}, + ValidationError("chart.metadata.name \"..\" is not allowed"), + }, { "chart without name", &Metadata{Name: "../../test", APIVersion: "v2", Version: "1.0"}, diff --git a/pkg/chart/v2/util/expand.go b/pkg/chart/v2/util/expand.go index 077dfbf38..6022d5869 100644 --- a/pkg/chart/v2/util/expand.go +++ b/pkg/chart/v2/util/expand.go @@ -52,6 +52,17 @@ func Expand(dir string, r io.Reader) error { return errors.New("chart name not specified") } + // Reject chart names that are POSIX path dot-segments or dot-dot segments or contain path separators. + // A dot-segment name (e.g. ".") causes SecureJoin to resolve to the root + // directory and extraction then to write files directly into that extraction root + // instead of a per-chart subdirectory. + if chartName == "." || chartName == ".." { + return fmt.Errorf("chart name %q is not allowed", chartName) + } + if chartName != filepath.Base(chartName) { + return fmt.Errorf("chart name %q must not contain path separators", chartName) + } + // Find the base directory // The directory needs to be cleaned prior to passing to SecureJoin or the location may end up // being wrong or returning an error. This was introduced in v0.4.0. @@ -61,6 +72,12 @@ func Expand(dir string, r io.Reader) error { return err } + // Defense-in-depth: the chart directory must be a subdirectory of dir, + // never dir itself. + if chartdir == dir { + return fmt.Errorf("chart name %q resolves to the extraction root", chartName) + } + // Copy all files verbatim. We don't parse these files because parsing can remove // comments. for _, file := range files { diff --git a/pkg/chart/v2/util/expand_test.go b/pkg/chart/v2/util/expand_test.go index 280995f7e..e9e298b81 100644 --- a/pkg/chart/v2/util/expand_test.go +++ b/pkg/chart/v2/util/expand_test.go @@ -17,11 +17,73 @@ limitations under the License. package util import ( + "archive/tar" + "bytes" + "compress/gzip" + "io/fs" "os" "path/filepath" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +// makeTestChartArchive builds a gzipped tar archive from the given sourceDir directory, file entries are prefixed with the given chartName +func makeTestChartArchive(t *testing.T, chartName, sourceDir string) *bytes.Buffer { + t.Helper() + + var result bytes.Buffer + gw := gzip.NewWriter(&result) + tw := tar.NewWriter(gw) + + dir := os.DirFS(sourceDir) + + writeFile := func(relPath string) { + t.Helper() + f, err := dir.Open(relPath) + require.NoError(t, err) + + fStat, err := f.Stat() + require.NoError(t, err) + + err = tw.WriteHeader(&tar.Header{ + Name: filepath.Join(chartName, relPath), + Mode: int64(fStat.Mode()), + Size: fStat.Size(), + }) + require.NoError(t, err) + + data, err := fs.ReadFile(dir, relPath) + require.NoError(t, err) + tw.Write(data) + } + + err := fs.WalkDir(dir, ".", func(path string, d os.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + + if d.IsDir() { + return nil + } + + writeFile(path) + + return nil + }) + if err != nil { + t.Fatal(err) + } + + err = tw.Close() + require.NoError(t, err) + err = gw.Close() + require.NoError(t, err) + + return &result +} + func TestExpand(t *testing.T) { dest := t.TempDir() @@ -75,6 +137,28 @@ func TestExpand(t *testing.T) { } } +func TestExpandError(t *testing.T) { + tests := map[string]struct { + chartName string + chartDir string + wantErr string + }{ + "dot name": {"dotname", "testdata/dotname", "not allowed"}, + "dotdot name": {"dotdotname", "testdata/dotdotname", "not allowed"}, + "slash in name": {"slashinname", "testdata/slashinname", "must not contain path separators"}, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + archive := makeTestChartArchive(t, tt.chartName, tt.chartDir) + + dest := t.TempDir() + err := Expand(dest, archive) + assert.ErrorContains(t, err, tt.wantErr) + }) + } +} + func TestExpandFile(t *testing.T) { dest := t.TempDir() diff --git a/pkg/chart/v2/util/testdata/dotdotname/Chart.yaml b/pkg/chart/v2/util/testdata/dotdotname/Chart.yaml new file mode 100644 index 000000000..9b081f27b --- /dev/null +++ b/pkg/chart/v2/util/testdata/dotdotname/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v3 +name: .. +description: A Helm chart for Kubernetes +version: 0.1.0 \ No newline at end of file diff --git a/pkg/chart/v2/util/testdata/dotname/Chart.yaml b/pkg/chart/v2/util/testdata/dotname/Chart.yaml new file mode 100644 index 000000000..597c16290 --- /dev/null +++ b/pkg/chart/v2/util/testdata/dotname/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v3 +name: . +description: A Helm chart for Kubernetes +version: 0.1.0 \ No newline at end of file diff --git a/pkg/chart/v2/util/testdata/slashinname/Chart.yaml b/pkg/chart/v2/util/testdata/slashinname/Chart.yaml new file mode 100644 index 000000000..0c522a4b6 --- /dev/null +++ b/pkg/chart/v2/util/testdata/slashinname/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v3 +name: a/../b +description: A Helm chart for Kubernetes +version: 0.1.0 \ No newline at end of file From 259f181808f267493d56eccd7f6191f78225a6fa Mon Sep 17 00:00:00 2001 From: tsinglua Date: Wed, 11 Mar 2026 16:55:34 +0800 Subject: [PATCH 220/571] chore: fix some minor issues in the comments Signed-off-by: tsinglua --- internal/plugin/verify.go | 2 +- pkg/kube/client.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/plugin/verify.go b/internal/plugin/verify.go index 760a56e67..df31fc4ce 100644 --- a/internal/plugin/verify.go +++ b/internal/plugin/verify.go @@ -33,7 +33,7 @@ func VerifyPlugin(archiveData, provData []byte, filename, keyring string) (*prov return sig.Verify(archiveData, provData, filename) } -// isTarball checks if a file has a tarball extension +// IsTarball checks if a file has a tarball extension func IsTarball(filename string) bool { return filepath.Ext(filename) == ".gz" || filepath.Ext(filename) == ".tgz" } diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 5efb03cb0..69de3b0f3 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -272,7 +272,7 @@ type ClientCreateOption func(*clientCreateOptions) error // ClientCreateOptionServerSideApply enables performing object apply server-side // see: https://kubernetes.io/docs/reference/using-api/server-side-apply/ // -// `forceConflicts` forces conflicts to be resolved (may be when serverSideApply enabled only) +// `forceConflicts` forces conflicts to be resolved (may be used when serverSideApply enabled only) // see: https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts func ClientCreateOptionServerSideApply(serverSideApply, forceConflicts bool) ClientCreateOption { return func(o *clientCreateOptions) error { From c4be7af2a14c1a01f21231ebb5dd41806fcb0797 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 21:34:17 +0000 Subject: [PATCH 221/571] chore(deps): bump golang.org/x/term from 0.40.0 to 0.41.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.40.0 to 0.41.0. - [Commits](https://github.com/golang/term/compare/v0.40.0...v0.41.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-version: 0.41.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 65344ea63..bdad47a64 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/tetratelabs/wazero v1.11.0 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.48.0 - golang.org/x/term v0.40.0 + golang.org/x/term v0.41.0 golang.org/x/text v0.34.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.35.1 @@ -162,7 +162,7 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect + golang.org/x/sys v0.42.0 // indirect golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.41.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect diff --git a/go.sum b/go.sum index 04bfe426f..2ec3731df 100644 --- a/go.sum +++ b/go.sum @@ -430,8 +430,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -439,8 +439,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= From 36dcc27ca3c0cd6d0d08713b03dca82f43d7c5f9 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Thu, 5 Mar 2026 08:14:37 -0800 Subject: [PATCH 222/571] fix: Plugin version path traversal Signed-off-by: George Jenkins --- internal/plugin/loader_test.go | 1 + internal/plugin/metadata.go | 12 +++ internal/plugin/metadata_legacy.go | 5 ++ internal/plugin/metadata_legacy_test.go | 15 ++++ internal/plugin/metadata_test.go | 34 ++++++++ internal/plugin/metadata_v1.go | 7 ++ internal/plugin/metadata_v1_test.go | 82 +++++++++++++++++++ .../helm/plugins/fullenv/plugin.yaml | 1 + .../helmhome/helm/plugins/args/plugin.yaml | 1 + .../helmhome/helm/plugins/echo/plugin.yaml | 1 + .../helm/plugins/exitwith/plugin.yaml | 1 + .../helmhome/helm/plugins/fullenv/plugin.yaml | 1 + .../helm/plugins/shortenv/plugin.yaml | 1 + pkg/cmd/testdata/testplugin/plugin.yaml | 1 + 14 files changed, 163 insertions(+) create mode 100644 internal/plugin/metadata_v1_test.go diff --git a/internal/plugin/loader_test.go b/internal/plugin/loader_test.go index 03ef02c85..7b7f280a3 100644 --- a/internal/plugin/loader_test.go +++ b/internal/plugin/loader_test.go @@ -337,6 +337,7 @@ runtime: subprocess "correct name field": { yaml: `apiVersion: v1 name: my-plugin +version: 1.0.0 type: cli/v1 runtime: subprocess `, diff --git a/internal/plugin/metadata.go b/internal/plugin/metadata.go index ef7d54c3a..a49408084 100644 --- a/internal/plugin/metadata.go +++ b/internal/plugin/metadata.go @@ -19,9 +19,17 @@ import ( "errors" "fmt" + "github.com/Masterminds/semver/v3" + "helm.sh/helm/v4/internal/plugin/schema" ) +// isValidSemver checks if the given string is a valid semantic version. +func isValidSemver(v string) bool { + _, err := semver.NewVersion(v) + return err == nil +} + // Metadata of a plugin, converted from the "on-disk" legacy or v1 plugin.yaml // Specifically, Config and RuntimeConfig are converted to their respective types based on the plugin type and runtime type Metadata struct { @@ -57,6 +65,10 @@ func (m Metadata) Validate() error { errs = append(errs, fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name)) } + if m.Version != "" && !isValidSemver(m.Version) { + errs = append(errs, fmt.Errorf("invalid plugin version %q: must be valid semver", m.Version)) + } + if m.APIVersion == "" { errs = append(errs, errors.New("empty APIVersion")) } diff --git a/internal/plugin/metadata_legacy.go b/internal/plugin/metadata_legacy.go index b5849edeb..cdde475bb 100644 --- a/internal/plugin/metadata_legacy.go +++ b/internal/plugin/metadata_legacy.go @@ -72,6 +72,11 @@ func (m *MetadataLegacy) Validate() error { if !validPluginName.MatchString(m.Name) { return fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name) } + + if m.Version != "" && !isValidSemver(m.Version) { + return fmt.Errorf("invalid plugin version %q: must be valid semver", m.Version) + } + m.Usage = sanitizeString(m.Usage) if len(m.PlatformCommand) > 0 && len(m.Command) > 0 { diff --git a/internal/plugin/metadata_legacy_test.go b/internal/plugin/metadata_legacy_test.go index 9421e98b5..24cc48a60 100644 --- a/internal/plugin/metadata_legacy_test.go +++ b/internal/plugin/metadata_legacy_test.go @@ -59,6 +59,13 @@ func TestMetadataLegacyValidate(t *testing.T) { }, }, }, + "valid with version": { + Name: "myplugin", + Version: "1.0.0", + }, + "valid with empty version": { + Name: "myplugin", + }, } for testName, metadata := range testsValid { @@ -116,6 +123,14 @@ func TestMetadataLegacyValidate(t *testing.T) { }, }, }, + "path traversal version": { + Name: "myplugin", + Version: "../../../../tmp/evil", + }, + "invalid version": { + Name: "myplugin", + Version: "not-a-version", + }, } for testName, metadata := range testsInvalid { diff --git a/internal/plugin/metadata_test.go b/internal/plugin/metadata_test.go index 145ef5101..e983d32ae 100644 --- a/internal/plugin/metadata_test.go +++ b/internal/plugin/metadata_test.go @@ -72,6 +72,40 @@ func TestValidatePluginData(t *testing.T) { } } +func TestMetadataValidateVersionPathTraversal(t *testing.T) { + tests := map[string]struct { + version string + valid bool + }{ + "valid semver": {version: "1.0.0", valid: true}, + "valid semver with v prefix": {version: "v1.0.0", valid: true}, + "valid semver with prerelease": {version: "1.2.3-alpha.1+build.123", valid: true}, + "empty version": {version: "", valid: true}, + "path traversal": {version: "../../../../tmp/evil", valid: false}, + "path traversal etc": {version: "../../../etc/passwd", valid: false}, + "path traversal in version": {version: "1.0.0/../../etc", valid: false}, + "not a version": {version: "not-a-version", valid: false}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + m := mockSubprocessCLIPlugin(t, "testplugin") + m.metadata.Version = tc.version + err := m.Metadata().Validate() + if tc.valid && err != nil { + t.Errorf("expected valid, got error: %s", err) + } + if !tc.valid { + if err == nil { + t.Errorf("expected error for version %q, got nil", tc.version) + } else if !strings.Contains(err.Error(), "invalid plugin version") { + t.Errorf("expected 'invalid plugin version' error, got: %s", err) + } + } + }) + } +} + func TestMetadataValidateMultipleErrors(t *testing.T) { // Create metadata with multiple validation issues metadata := Metadata{ diff --git a/internal/plugin/metadata_v1.go b/internal/plugin/metadata_v1.go index 77f23e154..81d4a8a70 100644 --- a/internal/plugin/metadata_v1.go +++ b/internal/plugin/metadata_v1.go @@ -52,6 +52,13 @@ func (m *MetadataV1) Validate() error { return errors.New("invalid plugin `name`") } + if m.Version == "" { + return errors.New("plugin `version` is required") + } + if !isValidSemver(m.Version) { + return fmt.Errorf("invalid plugin `version` %q: must be valid semver", m.Version) + } + if m.APIVersion != "v1" { return fmt.Errorf("invalid `apiVersion`: %q", m.APIVersion) } diff --git a/internal/plugin/metadata_v1_test.go b/internal/plugin/metadata_v1_test.go new file mode 100644 index 000000000..ed9a2c671 --- /dev/null +++ b/internal/plugin/metadata_v1_test.go @@ -0,0 +1,82 @@ +/* +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 plugin + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMetadataV1ValidateVersion(t *testing.T) { + base := func() MetadataV1 { + return MetadataV1{ + APIVersion: "v1", + Name: "myplugin", + Type: "cli/v1", + Runtime: "subprocess", + Version: "1.0.0", + } + } + + testsValid := map[string]string{ + "simple version": "1.0.0", + "v prefix": "v1.0.0", + "with prerelease": "1.2.3-alpha.1", + "with build meta": "1.2.3+build.123", + "full prerelease": "1.2.3-alpha.1+build.123", + } + + for name, version := range testsValid { + t.Run("valid/"+name, func(t *testing.T) { + m := base() + m.Version = version + assert.NoError(t, m.Validate()) + }) + } + + testsInvalid := map[string]struct { + version string + errMsg string + }{ + "empty version": { + version: "", + errMsg: "plugin `version` is required", + }, + "path traversal": { + version: "../../../../tmp/evil", + errMsg: "invalid plugin `version`", + }, + "path traversal etc": { + version: "../../../etc/passwd", + errMsg: "invalid plugin `version`", + }, + "not a version": { + version: "not-a-version", + errMsg: "invalid plugin `version`", + }, + } + + for name, tc := range testsInvalid { + t.Run("invalid/"+name, func(t *testing.T) { + m := base() + m.Version = tc.version + err := m.Validate() + assert.Error(t, err) + assert.Contains(t, err.Error(), tc.errMsg) + }) + } +} diff --git a/pkg/cmd/testdata/helm home with space/helm/plugins/fullenv/plugin.yaml b/pkg/cmd/testdata/helm home with space/helm/plugins/fullenv/plugin.yaml index a58544b03..c99e5122b 100644 --- a/pkg/cmd/testdata/helm home with space/helm/plugins/fullenv/plugin.yaml +++ b/pkg/cmd/testdata/helm home with space/helm/plugins/fullenv/plugin.yaml @@ -1,6 +1,7 @@ --- apiVersion: v1 name: fullenv +version: 0.1.0 type: cli/v1 runtime: subprocess config: diff --git a/pkg/cmd/testdata/helmhome/helm/plugins/args/plugin.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/args/plugin.yaml index 4156e7f17..24d79ac7e 100644 --- a/pkg/cmd/testdata/helmhome/helm/plugins/args/plugin.yaml +++ b/pkg/cmd/testdata/helmhome/helm/plugins/args/plugin.yaml @@ -1,4 +1,5 @@ name: args +version: 0.1.0 type: cli/v1 apiVersion: v1 runtime: subprocess diff --git a/pkg/cmd/testdata/helmhome/helm/plugins/echo/plugin.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/echo/plugin.yaml index a0a0b5255..a707c3373 100644 --- a/pkg/cmd/testdata/helmhome/helm/plugins/echo/plugin.yaml +++ b/pkg/cmd/testdata/helmhome/helm/plugins/echo/plugin.yaml @@ -1,4 +1,5 @@ name: echo +version: 0.1.0 type: cli/v1 apiVersion: v1 runtime: subprocess diff --git a/pkg/cmd/testdata/helmhome/helm/plugins/exitwith/plugin.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/exitwith/plugin.yaml index ba9508255..93930219b 100644 --- a/pkg/cmd/testdata/helmhome/helm/plugins/exitwith/plugin.yaml +++ b/pkg/cmd/testdata/helmhome/helm/plugins/exitwith/plugin.yaml @@ -1,6 +1,7 @@ --- apiVersion: v1 name: exitwith +version: 0.1.0 type: cli/v1 runtime: subprocess config: diff --git a/pkg/cmd/testdata/helmhome/helm/plugins/fullenv/plugin.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/fullenv/plugin.yaml index a58544b03..c99e5122b 100644 --- a/pkg/cmd/testdata/helmhome/helm/plugins/fullenv/plugin.yaml +++ b/pkg/cmd/testdata/helmhome/helm/plugins/fullenv/plugin.yaml @@ -1,6 +1,7 @@ --- apiVersion: v1 name: fullenv +version: 0.1.0 type: cli/v1 runtime: subprocess config: diff --git a/pkg/cmd/testdata/helmhome/helm/plugins/shortenv/plugin.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/shortenv/plugin.yaml index 5fe053ed0..3f935db4b 100644 --- a/pkg/cmd/testdata/helmhome/helm/plugins/shortenv/plugin.yaml +++ b/pkg/cmd/testdata/helmhome/helm/plugins/shortenv/plugin.yaml @@ -1,6 +1,7 @@ --- apiVersion: v1 name: shortenv +version: 0.1.0 type: cli/v1 runtime: subprocess config: diff --git a/pkg/cmd/testdata/testplugin/plugin.yaml b/pkg/cmd/testdata/testplugin/plugin.yaml index 3ee5d04f6..fb1d82062 100644 --- a/pkg/cmd/testdata/testplugin/plugin.yaml +++ b/pkg/cmd/testdata/testplugin/plugin.yaml @@ -1,6 +1,7 @@ --- apiVersion: v1 name: testplugin +version: 0.1.0 type: cli/v1 runtime: subprocess config: From d7cdc9e8fb20c42d19a2371f37ee719be6be6b94 Mon Sep 17 00:00:00 2001 From: abhay1999 Date: Thu, 12 Mar 2026 08:02:50 +0530 Subject: [PATCH 223/571] pkg/kube: remove legacy import comments Import path comments (e.g. `// import "helm.sh/helm/v4/pkg/kube"`) are a pre-Go modules convention no longer needed in module-aware builds. Some files in pkg/kube had these comments while others did not, causing inconsistency that triggered downstream Kythe indexing errors. Remove the import comments from all affected files to make the package declaration consistent across the directory. Fixes #31846 Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: abhay1999 --- pkg/kube/client.go | 2 +- pkg/kube/converter.go | 2 +- pkg/kube/factory.go | 2 +- pkg/kube/ready.go | 2 +- pkg/kube/resource.go | 2 +- pkg/kube/resource_policy.go | 2 +- pkg/kube/statuswait.go | 2 +- pkg/kube/wait.go | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 69de3b0f3..44f31cdbe 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v4/pkg/kube" +package kube import ( "bytes" diff --git a/pkg/kube/converter.go b/pkg/kube/converter.go index ac6d95fb4..7300a708c 100644 --- a/pkg/kube/converter.go +++ b/pkg/kube/converter.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v4/pkg/kube" +package kube import ( "sync" diff --git a/pkg/kube/factory.go b/pkg/kube/factory.go index 1d237c307..4e12e0889 100644 --- a/pkg/kube/factory.go +++ b/pkg/kube/factory.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v4/pkg/kube" +package kube import ( "k8s.io/cli-runtime/pkg/resource" diff --git a/pkg/kube/ready.go b/pkg/kube/ready.go index bfa98504c..a1a3d4a9a 100644 --- a/pkg/kube/ready.go +++ b/pkg/kube/ready.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v4/pkg/kube" +package kube import ( "context" diff --git a/pkg/kube/resource.go b/pkg/kube/resource.go index d6d08d589..bc0fff9a2 100644 --- a/pkg/kube/resource.go +++ b/pkg/kube/resource.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v4/pkg/kube" +package kube import "k8s.io/cli-runtime/pkg/resource" diff --git a/pkg/kube/resource_policy.go b/pkg/kube/resource_policy.go index fb1089785..3a271877c 100644 --- a/pkg/kube/resource_policy.go +++ b/pkg/kube/resource_policy.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v4/pkg/kube" +package kube // ResourcePolicyAnno is the annotation name for a resource policy const ResourcePolicyAnno = "helm.sh/resource-policy" diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index 28ee84bc2..59c1218ff 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v4/pkg/kube" +package kube import ( "context" diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go index 8b4b2ed1b..cb71d4fc2 100644 --- a/pkg/kube/wait.go +++ b/pkg/kube/wait.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v4/pkg/kube" +package kube import ( "context" From e64d628a139fab8c876a1d2f4c2928096b286bed Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Thu, 12 Mar 2026 06:12:04 -0600 Subject: [PATCH 224/571] fix(kube): remove legacy import comments from test files Remove pre-Go modules import path comments from pkg/kube test files (ready_test.go, resource_test.go, statuswait_test.go) for consistency with the rest of the package. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Terry Howe --- pkg/kube/ready_test.go | 2 +- pkg/kube/resource_test.go | 2 +- pkg/kube/statuswait_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/kube/ready_test.go b/pkg/kube/ready_test.go index db0d02cbe..3b80e95d2 100644 --- a/pkg/kube/ready_test.go +++ b/pkg/kube/ready_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v4/pkg/kube" +package kube import ( "context" diff --git a/pkg/kube/resource_test.go b/pkg/kube/resource_test.go index 41215a878..283b44945 100644 --- a/pkg/kube/resource_test.go +++ b/pkg/kube/resource_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v4/pkg/kube" +package kube import ( "testing" diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index 4b4928d4a..0639e07fc 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube // import "helm.sh/helm/v4/pkg/kube" +package kube import ( "context" From 3d06fd1feb37f11d050e78f7c17df3c713fcd344 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 21:34:16 +0000 Subject: [PATCH 225/571] chore(deps): bump golang.org/x/crypto from 0.48.0 to 0.49.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.48.0 to 0.49.0. - [Commits](https://github.com/golang/crypto/compare/v0.48.0...v0.49.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.49.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index bdad47a64..9f70910f0 100644 --- a/go.mod +++ b/go.mod @@ -35,9 +35,9 @@ require ( github.com/stretchr/testify v1.11.1 github.com/tetratelabs/wazero v1.11.0 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.48.0 + golang.org/x/crypto v0.49.0 golang.org/x/term v0.41.0 - golang.org/x/text v0.34.0 + golang.org/x/text v0.35.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.35.1 k8s.io/apiextensions-apiserver v0.35.1 @@ -158,13 +158,13 @@ require ( go.opentelemetry.io/otel/trace v1.40.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/net v0.49.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.51.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect - golang.org/x/sync v0.19.0 // indirect + golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.41.0 // indirect + golang.org/x/tools v0.42.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/grpc v1.78.0 // indirect diff --git a/go.sum b/go.sum index 2ec3731df..956c51e1b 100644 --- a/go.sum +++ b/go.sum @@ -381,14 +381,14 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -399,8 +399,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -411,8 +411,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -448,8 +448,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -458,8 +458,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= From 47a084091eeb1c5de221e061866b319f5b5f99f5 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Thu, 12 Mar 2026 14:30:41 -0700 Subject: [PATCH 226/571] ignore error plugin loads (cli, getter) Signed-off-by: George Jenkins --- cmd/helm/helm_test.go | 2 +- internal/plugin/loader.go | 37 +++++++++++--- internal/plugin/loader_test.go | 30 +++++------- internal/plugin/metadata.go | 5 +- internal/plugin/metadata_legacy_test.go | 4 ++ internal/plugin/metadata_test.go | 49 ++++++++++--------- internal/plugin/metadata_v1_test.go | 13 +++-- internal/plugin/plugin_test.go | 2 +- internal/plugin/runtime_subprocess_test.go | 2 +- pkg/cmd/plugin_test.go | 1 + pkg/cmd/plugin_uninstall.go | 2 +- pkg/cmd/plugin_update.go | 2 +- .../helm/plugins/noversion/plugin.yaml | 7 +++ 13 files changed, 95 insertions(+), 61 deletions(-) create mode 100644 pkg/cmd/testdata/helmhome/helm/plugins/noversion/plugin.yaml diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index 60addadb1..a9362f772 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -69,7 +69,7 @@ func TestCliPluginExitCode(t *testing.T) { assert.Empty(t, stdout.String()) - expectedStderr := "Error: plugin \"exitwith\" exited with error\n" + expectedStderr := "level=WARN msg=\"failed to load plugin (ignoring)\" plugin_yaml=../../pkg/cmd/testdata/helmhome/helm/plugins/noversion/plugin.yaml error=\"failed to load plugin \\\"../../pkg/cmd/testdata/helmhome/helm/plugins/noversion\\\": plugin `version` is required\"\nError: plugin \"exitwith\" exited with error\n" if stderr.String() != expectedStderr { t.Errorf("Expected %q written to stderr: Got %q", expectedStderr, stderr.String()) } diff --git a/internal/plugin/loader.go b/internal/plugin/loader.go index 2f051b99e..d99395641 100644 --- a/internal/plugin/loader.go +++ b/internal/plugin/loader.go @@ -19,6 +19,7 @@ import ( "bytes" "fmt" "io" + "log/slog" "os" "path/filepath" @@ -158,18 +159,27 @@ func LoadDir(dirname string) (Plugin, error) { return pm.CreatePlugin(dirname, m) } -// LoadAll loads all plugins found beneath the base directory. +func LogIgnorePluginLoadErrorFilterFunc(pluginYAML string, err error) error { + slog.Warn("failed to load plugin (ignoring)", slog.String("plugin_yaml", pluginYAML), slog.Any("error", err)) + return nil +} + +// errorFilterFunc is a function that can filter errors during plugin loading +type ErrorFilterFunc func(string, error) error + +// LoadAllDir load all plugins found beneath the base directory, using the provided error filter to determine whether to fail on individual plugin load errors. // // This scans only one directory level. -func LoadAll(basedir string) ([]Plugin, error) { - var plugins []Plugin - // We want basedir/*/plugin.yaml +func LoadAllDir(basedir string, errorFilter ErrorFilterFunc) ([]Plugin, error) { + // We want /*/plugin.yaml scanpath := filepath.Join(basedir, "*", PluginFileName) matches, err := filepath.Glob(scanpath) if err != nil { return nil, fmt.Errorf("failed to search for plugins in %q: %w", scanpath, err) } + plugins := make([]Plugin, 0, len(matches)) + // empty dir should load if len(matches) == 0 { return plugins, nil @@ -179,9 +189,12 @@ func LoadAll(basedir string) ([]Plugin, error) { dir := filepath.Dir(yamlFile) p, err := LoadDir(dir) if err != nil { - return plugins, err + if errNew := errorFilter(yamlFile, err); errNew != nil { + return plugins, errNew + } + } else { + plugins = append(plugins, p) } - plugins = append(plugins, p) } return plugins, detectDuplicates(plugins) } @@ -193,8 +206,12 @@ type findFunc func(pluginsDir string) ([]Plugin, error) type filterFunc func(Plugin) bool // FindPlugins returns a list of plugins that match the descriptor +// Errors loading a plugin are ignored with a warning func FindPlugins(pluginsDirs []string, descriptor Descriptor) ([]Plugin, error) { - return findPlugins(pluginsDirs, LoadAll, makeDescriptorFilter(descriptor)) + loadAllIgnoreErrors := func(pluginsDir string) ([]Plugin, error) { + return LoadAllDir(pluginsDir, LogIgnorePluginLoadErrorFilterFunc) + } + return findPlugins(pluginsDirs, loadAllIgnoreErrors, makeDescriptorFilter(descriptor)) } // findPlugins is the internal implementation that uses the find and filter functions @@ -237,7 +254,11 @@ func makeDescriptorFilter(descriptor Descriptor) filterFunc { // FindPlugin returns a single plugin that matches the descriptor func FindPlugin(dirs []string, descriptor Descriptor) (Plugin, error) { - plugins, err := FindPlugins(dirs, descriptor) + loadAllIgnoreErrors := func(pluginsDir string) ([]Plugin, error) { + return LoadAllDir(pluginsDir, LogIgnorePluginLoadErrorFilterFunc) + } + + plugins, err := findPlugins(dirs, loadAllIgnoreErrors, makeDescriptorFilter(descriptor)) if err != nil { return nil, err } diff --git a/internal/plugin/loader_test.go b/internal/plugin/loader_test.go index 7b7f280a3..b98f87a07 100644 --- a/internal/plugin/loader_test.go +++ b/internal/plugin/loader_test.go @@ -204,16 +204,16 @@ func TestDetectDuplicates(t *testing.T) { } } -func TestLoadAll(t *testing.T) { - // Verify that empty dir loads: - { - plugs, err := LoadAll("testdata") - require.NoError(t, err) - assert.Len(t, plugs, 0) - } +func TestLoadAllDir_Empty(t *testing.T) { + emptyDir := t.TempDir() + plugs, err := LoadAllDir(emptyDir, func(_ string, err error) error { return err }) + require.NoError(t, err) + assert.Len(t, plugs, 0) +} +func TestLoadAllPluginsDir(t *testing.T) { basedir := "testdata/plugdir/good" - plugs, err := LoadAll(basedir) + plugs, err := LoadAllDir(basedir, func(_ string, err error) error { return err }) require.NoError(t, err) require.NotEmpty(t, plugs, "expected plugins to be loaded from %s", basedir) @@ -232,7 +232,7 @@ func TestLoadAll(t *testing.T) { assert.Contains(t, plugsMap, "postrenderer-v1") } -func TestFindPlugins(t *testing.T) { +func TestLoadAllPluginsDir_Zero(t *testing.T) { cases := []struct { name string plugdirs string @@ -240,28 +240,20 @@ func TestFindPlugins(t *testing.T) { }{ { name: "plugdirs is empty", - plugdirs: "", - expected: 0, + plugdirs: t.TempDir(), }, { name: "plugdirs isn't dir", plugdirs: "./plugin_test.go", - expected: 0, }, { name: "plugdirs doesn't have plugin", plugdirs: ".", - expected: 0, - }, - { - name: "normal", - plugdirs: "./testdata/plugdir/good", - expected: 7, }, } for _, c := range cases { t.Run(t.Name(), func(t *testing.T) { - plugin, err := LoadAll(c.plugdirs) + plugin, err := LoadAllDir(c.plugdirs, func(_ string, err error) error { return err }) require.NoError(t, err) assert.Len(t, plugin, c.expected, "expected %d plugins, got %d", c.expected, len(plugin)) }) diff --git a/internal/plugin/metadata.go b/internal/plugin/metadata.go index a49408084..562861b1c 100644 --- a/internal/plugin/metadata.go +++ b/internal/plugin/metadata.go @@ -24,9 +24,9 @@ import ( "helm.sh/helm/v4/internal/plugin/schema" ) -// isValidSemver checks if the given string is a valid semantic version. +// isValidSemver checks if the given string is a valid semantic version func isValidSemver(v string) bool { - _, err := semver.NewVersion(v) + _, err := semver.StrictNewVersion(v) return err == nil } @@ -65,6 +65,7 @@ func (m Metadata) Validate() error { errs = append(errs, fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name)) } + // Require version to be valid semver if specified if m.Version != "" && !isValidSemver(m.Version) { errs = append(errs, fmt.Errorf("invalid plugin version %q: must be valid semver", m.Version)) } diff --git a/internal/plugin/metadata_legacy_test.go b/internal/plugin/metadata_legacy_test.go index 24cc48a60..0ecb7e619 100644 --- a/internal/plugin/metadata_legacy_test.go +++ b/internal/plugin/metadata_legacy_test.go @@ -26,6 +26,10 @@ func TestMetadataLegacyValidate(t *testing.T) { "valid metadata": { Name: "myplugin", }, + "valid metadata (empty version)": { + Name: "myplugin", + Version: "", + }, "valid with command": { Name: "myplugin", Command: "echo hello", diff --git a/internal/plugin/metadata_test.go b/internal/plugin/metadata_test.go index e983d32ae..a2acd7925 100644 --- a/internal/plugin/metadata_test.go +++ b/internal/plugin/metadata_test.go @@ -18,6 +18,8 @@ package plugin import ( "strings" "testing" + + "github.com/stretchr/testify/assert" ) func TestValidatePluginData(t *testing.T) { @@ -72,36 +74,39 @@ func TestValidatePluginData(t *testing.T) { } } -func TestMetadataValidateVersionPathTraversal(t *testing.T) { - tests := map[string]struct { +func TestMetadataValidateVersion(t *testing.T) { + testValid := map[string]struct { + version string + }{ + "valid semver": {version: "1.0.0"}, + "valid semver with prerelease": {version: "1.2.3-alpha.1+build.123"}, + "empty version": {version: ""}, + } + + testInvalid := map[string]struct { version string - valid bool }{ - "valid semver": {version: "1.0.0", valid: true}, - "valid semver with v prefix": {version: "v1.0.0", valid: true}, - "valid semver with prerelease": {version: "1.2.3-alpha.1+build.123", valid: true}, - "empty version": {version: "", valid: true}, - "path traversal": {version: "../../../../tmp/evil", valid: false}, - "path traversal etc": {version: "../../../etc/passwd", valid: false}, - "path traversal in version": {version: "1.0.0/../../etc", valid: false}, - "not a version": {version: "not-a-version", valid: false}, + "valid semver with v prefix": {version: "v1.0.0"}, + "path traversal": {version: "../../../../tmp/evil"}, + "path traversal in version": {version: "1.0.0/../../etc"}, + "not a version": {version: "not-a-version"}, + } + + for name, tc := range testValid { + t.Run(name, func(t *testing.T) { + m := mockSubprocessCLIPlugin(t, "testplugin") + m.metadata.Version = tc.version + err := m.Metadata().Validate() + assert.NoError(t, err) + }) } - for name, tc := range tests { + for name, tc := range testInvalid { t.Run(name, func(t *testing.T) { m := mockSubprocessCLIPlugin(t, "testplugin") m.metadata.Version = tc.version err := m.Metadata().Validate() - if tc.valid && err != nil { - t.Errorf("expected valid, got error: %s", err) - } - if !tc.valid { - if err == nil { - t.Errorf("expected error for version %q, got nil", tc.version) - } else if !strings.Contains(err.Error(), "invalid plugin version") { - t.Errorf("expected 'invalid plugin version' error, got: %s", err) - } - } + assert.ErrorContains(t, err, "invalid plugin version") }) } } diff --git a/internal/plugin/metadata_v1_test.go b/internal/plugin/metadata_v1_test.go index ed9a2c671..17a02dac0 100644 --- a/internal/plugin/metadata_v1_test.go +++ b/internal/plugin/metadata_v1_test.go @@ -33,11 +33,10 @@ func TestMetadataV1ValidateVersion(t *testing.T) { } testsValid := map[string]string{ - "simple version": "1.0.0", - "v prefix": "v1.0.0", - "with prerelease": "1.2.3-alpha.1", - "with build meta": "1.2.3+build.123", - "full prerelease": "1.2.3-alpha.1+build.123", + "simple version": "1.0.0", + "with prerelease": "1.2.3-alpha.1", + "with build meta": "1.2.3+build.123", + "full prerelease": "1.2.3-alpha.1+build.123", } for name, version := range testsValid { @@ -56,6 +55,10 @@ func TestMetadataV1ValidateVersion(t *testing.T) { version: "", errMsg: "plugin `version` is required", }, + "v prefix": { + version: "v1.0.0", + errMsg: "invalid plugin `version` \"v1.0.0\": must be valid semver", + }, "path traversal": { version: "../../../../tmp/evil", errMsg: "invalid plugin `version`", diff --git a/internal/plugin/plugin_test.go b/internal/plugin/plugin_test.go index ae0b343f3..2e3f274f2 100644 --- a/internal/plugin/plugin_test.go +++ b/internal/plugin/plugin_test.go @@ -82,7 +82,7 @@ func mockSubprocessCLIPlugin(t *testing.T, pluginName string) *SubprocessPluginR return &SubprocessPluginRuntime{ metadata: Metadata{ Name: pluginName, - Version: "v0.1.2", + Version: "0.1.2", Type: "cli/v1", APIVersion: "v1", Runtime: "subprocess", diff --git a/internal/plugin/runtime_subprocess_test.go b/internal/plugin/runtime_subprocess_test.go index 271e79661..c651dd3f7 100644 --- a/internal/plugin/runtime_subprocess_test.go +++ b/internal/plugin/runtime_subprocess_test.go @@ -42,7 +42,7 @@ func mockSubprocessCLIPluginErrorExit(t *testing.T, pluginName string, exitCode md := Metadata{ Name: pluginName, - Version: "v0.1.2", + Version: "0.1.2", Type: "cli/v1", APIVersion: "v1", Runtime: "subprocess", diff --git a/pkg/cmd/plugin_test.go b/pkg/cmd/plugin_test.go index a250ba221..81df8502d 100644 --- a/pkg/cmd/plugin_test.go +++ b/pkg/cmd/plugin_test.go @@ -117,6 +117,7 @@ func TestLoadCLIPlugins(t *testing.T) { {"exitwith", "exitwith code", "This exits with the specified exit code", "", []string{"2"}, 2}, {"fullenv", "show env vars", "show all env vars", fullEnvOutput, []string{}, 0}, {"shortenv", "env stuff", "show the env", "HELM_PLUGIN_NAME=shortenv\n", []string{}, 0}, + // "noversion": plugin is invalid, and should not be loaded } pluginCmds := cmd.Commands() diff --git a/pkg/cmd/plugin_uninstall.go b/pkg/cmd/plugin_uninstall.go index 85eb46219..c75cf6264 100644 --- a/pkg/cmd/plugin_uninstall.go +++ b/pkg/cmd/plugin_uninstall.go @@ -62,7 +62,7 @@ func (o *pluginUninstallOptions) complete(args []string) error { func (o *pluginUninstallOptions) run(out io.Writer) error { slog.Debug("loading installer plugins", "dir", settings.PluginsDirectory) - plugins, err := plugin.LoadAll(settings.PluginsDirectory) + plugins, err := plugin.LoadAllDir(settings.PluginsDirectory, plugin.LogIgnorePluginLoadErrorFilterFunc) if err != nil { return err } diff --git a/pkg/cmd/plugin_update.go b/pkg/cmd/plugin_update.go index 6cc2729fc..83ef35107 100644 --- a/pkg/cmd/plugin_update.go +++ b/pkg/cmd/plugin_update.go @@ -62,7 +62,7 @@ func (o *pluginUpdateOptions) complete(args []string) error { func (o *pluginUpdateOptions) run(out io.Writer) error { slog.Debug("loading installed plugins", "path", settings.PluginsDirectory) - plugins, err := plugin.LoadAll(settings.PluginsDirectory) + plugins, err := plugin.LoadAllDir(settings.PluginsDirectory, plugin.LogIgnorePluginLoadErrorFilterFunc) if err != nil { return err } diff --git a/pkg/cmd/testdata/helmhome/helm/plugins/noversion/plugin.yaml b/pkg/cmd/testdata/helmhome/helm/plugins/noversion/plugin.yaml new file mode 100644 index 000000000..70c356dea --- /dev/null +++ b/pkg/cmd/testdata/helmhome/helm/plugins/noversion/plugin.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +name: noversion +type: cli/v1 +runtime: subprocess +runtimeConfig: + platformCommand: + - command: "echo hello" From 1e0f702f00d5fd36c9de9448e3271787bc588273 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 27 Feb 2026 19:10:53 +0100 Subject: [PATCH 227/571] chore: fix whitespace linter Signed-off-by: Matthieu MOREL --- .golangci.yml | 1 + internal/chart/v3/lint/lint.go | 1 - internal/chart/v3/lint/rules/template_test.go | 3 --- internal/chart/v3/loader/load_test.go | 2 -- internal/chart/v3/util/create.go | 1 - internal/chart/v3/util/dependencies_test.go | 1 - internal/chart/v3/util/validate_name.go | 1 - internal/cli/output/color_test.go | 3 --- internal/monocular/client.go | 2 -- internal/monocular/search.go | 1 - internal/monocular/search_test.go | 1 - internal/plugin/installer/base_test.go | 1 - internal/plugin/installer/extractor.go | 1 - internal/plugin/installer/http_installer_test.go | 5 ----- internal/plugin/installer/installer.go | 1 - internal/plugin/installer/vcs_installer_test.go | 1 - internal/plugin/loader.go | 5 ----- internal/plugin/loader_test.go | 1 - internal/plugin/metadata_test.go | 1 - internal/plugin/plugin_type_registry_test.go | 1 - internal/plugin/runtime_extismv1.go | 2 -- internal/plugin/subprocess_commands_test.go | 1 - internal/release/v2/util/manifest_sorter_test.go | 2 -- internal/resolver/resolver.go | 2 -- internal/third_party/dep/fs/fs_test.go | 3 --- internal/version/version.go | 1 - pkg/action/action.go | 1 - pkg/action/dependency.go | 1 - pkg/action/get_metadata.go | 1 - pkg/action/hooks.go | 4 ---- pkg/action/install_test.go | 3 --- pkg/action/package_test.go | 1 - pkg/action/resource_policy.go | 1 - pkg/action/upgrade.go | 1 - pkg/action/upgrade_test.go | 2 -- pkg/action/validate_test.go | 1 - pkg/chart/common/capabilities.go | 1 - pkg/chart/common/util/coalesce_test.go | 2 -- pkg/chart/common/util/values_test.go | 1 - pkg/chart/loader/load.go | 1 - pkg/chart/v2/lint/lint.go | 1 - pkg/chart/v2/lint/rules/template.go | 1 - pkg/chart/v2/lint/rules/template_test.go | 3 --- pkg/chart/v2/loader/load_test.go | 2 -- pkg/chart/v2/util/create.go | 1 - pkg/chart/v2/util/dependencies_test.go | 1 - pkg/chart/v2/util/validate_name.go | 1 - pkg/cmd/completion_test.go | 1 - pkg/cmd/flags.go | 1 - pkg/cmd/get_metadata.go | 1 - pkg/cmd/helpers.go | 1 - pkg/cmd/helpers_test.go | 2 -- pkg/cmd/history.go | 1 - pkg/cmd/lint_test.go | 1 - pkg/cmd/load_plugins.go | 1 - pkg/cmd/pull_test.go | 1 - pkg/cmd/repo_index_test.go | 1 - pkg/cmd/repo_remove_test.go | 1 - pkg/cmd/root.go | 2 -- pkg/cmd/search.go | 1 - pkg/cmd/search/search.go | 1 - pkg/cmd/search/search_test.go | 3 --- pkg/cmd/search_hub.go | 1 - pkg/cmd/search_hub_test.go | 2 -- pkg/cmd/template.go | 1 - pkg/cmd/uninstall.go | 1 - pkg/cmd/upgrade_test.go | 10 ---------- pkg/cmd/verify_test.go | 1 - pkg/downloader/chart_downloader.go | 2 -- pkg/downloader/manager.go | 3 --- pkg/engine/engine_test.go | 5 ----- pkg/getter/httpgetter_test.go | 1 - pkg/getter/plugingetter.go | 1 - pkg/helmpath/lazypath.go | 1 - pkg/kube/client.go | 8 -------- pkg/kube/client_test.go | 10 ---------- pkg/registry/chart.go | 6 ------ pkg/registry/chart_test.go | 9 --------- pkg/registry/client.go | 2 -- pkg/registry/registry_test.go | 1 - pkg/release/v1/util/manifest_sorter_test.go | 2 -- pkg/repo/v1/chartrepo.go | 1 - pkg/repo/v1/chartrepo_test.go | 2 -- pkg/repo/v1/index_test.go | 3 --- pkg/repo/v1/repotest/server_test.go | 2 -- pkg/storage/driver/memory_test.go | 1 - pkg/storage/driver/sql.go | 1 - pkg/strvals/parser.go | 1 - 88 files changed, 1 insertion(+), 172 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 536b4b212..fa021ade9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -36,6 +36,7 @@ linters: - unused - usestdlibvars - usetesting + - whitespace exclusions: diff --git a/internal/chart/v3/lint/lint.go b/internal/chart/v3/lint/lint.go index e98edfabe..ea8ca3d60 100644 --- a/internal/chart/v3/lint/lint.go +++ b/internal/chart/v3/lint/lint.go @@ -44,7 +44,6 @@ func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption { } func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter { - chartDir, _ := filepath.Abs(baseDir) lo := linterOptions{} diff --git a/internal/chart/v3/lint/rules/template_test.go b/internal/chart/v3/lint/rules/template_test.go index b1371659f..88343b330 100644 --- a/internal/chart/v3/lint/rules/template_test.go +++ b/internal/chart/v3/lint/rules/template_test.go @@ -242,7 +242,6 @@ data: // // See https://github.com/helm/helm/issues/7483 func TestStrictTemplateParsingMapError(t *testing.T) { - ch := chart.Chart{ Metadata: &chart.Metadata{ Name: "regression7483", @@ -371,7 +370,6 @@ func TestValidateTopIndentLevel(t *testing.T) { t.Errorf("Expected %t for %q", shouldFail, doc) } } - } // TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments @@ -463,5 +461,4 @@ func TestIsYamlFileExtension(t *testing.T) { t.Errorf("isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected) } } - } diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index c32f79aff..0ede21cfb 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -337,7 +337,6 @@ icon: https://example.com/64x64.png if text.String() != "" { t.Errorf("Expected no message to Stderr, got %s", text.String()) } - } // Packaging the chart on a Windows machine will produce an @@ -607,7 +606,6 @@ func verifyChart(t *testing.T, c *chart.Chart) { t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version) } } - } func verifyDependencies(t *testing.T, c *chart.Chart) { diff --git a/internal/chart/v3/util/create.go b/internal/chart/v3/util/create.go index 48d2120e5..b3d75ac2d 100644 --- a/internal/chart/v3/util/create.go +++ b/internal/chart/v3/util/create.go @@ -702,7 +702,6 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error { // error. In such a case, this will attempt to clean up by removing the // new chart directory. func Create(name, dir string) (string, error) { - // Sanity-check the name of a chart so user doesn't create one that causes problems. if err := validateChartName(name); err != nil { return "", err diff --git a/internal/chart/v3/util/dependencies_test.go b/internal/chart/v3/util/dependencies_test.go index c8a176725..0a0937e4a 100644 --- a/internal/chart/v3/util/dependencies_test.go +++ b/internal/chart/v3/util/dependencies_test.go @@ -459,7 +459,6 @@ func TestDependentChartAliases(t *testing.T) { if aliasChart := getAliasDependency(c.Dependencies(), req[2]); aliasChart != nil { t.Fatalf("expected no chart but got %s", aliasChart.Name()) } - } func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) { diff --git a/internal/chart/v3/util/validate_name.go b/internal/chart/v3/util/validate_name.go index 6595e085d..7d85a5b88 100644 --- a/internal/chart/v3/util/validate_name.go +++ b/internal/chart/v3/util/validate_name.go @@ -79,7 +79,6 @@ func ValidateReleaseName(name string) error { // This case is preserved for backwards compatibility if name == "" { return errMissingName - } if len(name) > maxReleaseNameLen || !validName.MatchString(name) { return errInvalidName diff --git a/internal/cli/output/color_test.go b/internal/cli/output/color_test.go index 3b8de39e8..f0484fc3a 100644 --- a/internal/cli/output/color_test.go +++ b/internal/cli/output/color_test.go @@ -24,7 +24,6 @@ import ( ) func TestColorizeStatus(t *testing.T) { - tests := []struct { name string status common.Status @@ -107,7 +106,6 @@ func TestColorizeStatus(t *testing.T) { } func TestColorizeHeader(t *testing.T) { - tests := []struct { name string header string @@ -149,7 +147,6 @@ func TestColorizeHeader(t *testing.T) { } func TestColorizeNamespace(t *testing.T) { - tests := []struct { name string namespace string diff --git a/internal/monocular/client.go b/internal/monocular/client.go index f4ef5d647..cd1a0d666 100644 --- a/internal/monocular/client.go +++ b/internal/monocular/client.go @@ -33,7 +33,6 @@ type Client struct { // New creates a new client func New(u string) (*Client, error) { - // Validate we have a URL if err := validate(u); err != nil { return nil, err @@ -46,7 +45,6 @@ func New(u string) (*Client, error) { // Validate if the base URL for monocular is valid. func validate(u string) error { - // Check if it is parsable p, err := url.Parse(u) if err != nil { diff --git a/internal/monocular/search.go b/internal/monocular/search.go index fcf04b7a4..cfae87ded 100644 --- a/internal/monocular/search.go +++ b/internal/monocular/search.go @@ -99,7 +99,6 @@ type ChartVersion struct { // Search performs a search against the monocular search API func (c *Client) Search(term string) ([]SearchResult, error) { - // Create the URL to the search endpoint // Note, this is currently an internal API for the Hub. This should be // formatted without showing how monocular operates. diff --git a/internal/monocular/search_test.go b/internal/monocular/search_test.go index fc82ef4b4..cdf67f69e 100644 --- a/internal/monocular/search_test.go +++ b/internal/monocular/search_test.go @@ -27,7 +27,6 @@ import ( var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` func TestSearch(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprintln(w, searchResult) })) diff --git a/internal/plugin/installer/base_test.go b/internal/plugin/installer/base_test.go index 62b77bde5..6df8ec8a1 100644 --- a/internal/plugin/installer/base_test.go +++ b/internal/plugin/installer/base_test.go @@ -35,7 +35,6 @@ func TestPath(t *testing.T) { } for _, tt := range tests { - t.Setenv("HELM_PLUGINS", tt.helmPluginsDir) baseIns := newBase(tt.source) baseInsPath := baseIns.Path() diff --git a/internal/plugin/installer/extractor.go b/internal/plugin/installer/extractor.go index b753dfbca..3af56ab56 100644 --- a/internal/plugin/installer/extractor.go +++ b/internal/plugin/installer/extractor.go @@ -90,7 +90,6 @@ func NewExtractor(source string) (Extractor, error) { // - Beginning a path with a path separator is illegal // - Rudimentary symlink protections are offered by SecureJoin. func cleanJoin(root, dest string) (string, error) { - // On Windows, this is a drive separator. On UNIX-like, this is the path list separator. // In neither case do we want to trust a TAR that contains these. if strings.Contains(dest, ":") { diff --git a/internal/plugin/installer/http_installer_test.go b/internal/plugin/installer/http_installer_test.go index 85a84ee31..9ae8fe7b5 100644 --- a/internal/plugin/installer/http_installer_test.go +++ b/internal/plugin/installer/http_installer_test.go @@ -124,7 +124,6 @@ func TestHTTPInstaller(t *testing.T) { } else if err.Error() != "plugin already exists" { t.Fatalf("expected error for plugin exists, got (%v)", err) } - } func TestHTTPInstallerNonExistentVersion(t *testing.T) { @@ -157,7 +156,6 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) { if err := Install(i); err == nil { t.Fatal("expected error from http client") } - } func TestHTTPInstallerUpdate(t *testing.T) { @@ -297,7 +295,6 @@ func TestExtract(t *testing.T) { t.Fatalf("Expected %s to have %o mode but has %o (umask: %o)", readmeFullPath, expectedReadmePerm, info.Mode().Perm(), currentUmask) } - } func TestCleanJoin(t *testing.T) { @@ -327,11 +324,9 @@ func TestCleanJoin(t *testing.T) { t.Errorf("Test %d: Expected %q but got %q", i, fixture.expect, out) } } - } func TestMediaTypeToExtension(t *testing.T) { - for mt, shouldPass := range map[string]bool{ "": false, "application/gzip": true, diff --git a/internal/plugin/installer/installer.go b/internal/plugin/installer/installer.go index f0870dcc5..54effb1cb 100644 --- a/internal/plugin/installer/installer.go +++ b/internal/plugin/installer/installer.go @@ -72,7 +72,6 @@ type VerificationResult struct { // InstallWithOptions installs a plugin with options. func InstallWithOptions(i Installer, opts Options) (*VerificationResult, error) { - if err := os.MkdirAll(filepath.Dir(i.Path()), 0755); err != nil { return nil, err } diff --git a/internal/plugin/installer/vcs_installer_test.go b/internal/plugin/installer/vcs_installer_test.go index 7fe627b59..54f94c724 100644 --- a/internal/plugin/installer/vcs_installer_test.go +++ b/internal/plugin/installer/vcs_installer_test.go @@ -185,5 +185,4 @@ func TestVCSInstallerUpdate(t *testing.T) { } else if err.Error() != "plugin repo was modified" { t.Fatalf("expected error for plugin modified, got (%v)", err) } - } diff --git a/internal/plugin/loader.go b/internal/plugin/loader.go index 2f051b99e..bd7edbc2c 100644 --- a/internal/plugin/loader.go +++ b/internal/plugin/loader.go @@ -44,7 +44,6 @@ func peekAPIVersion(r io.Reader) (string, error) { } func loadMetadataLegacy(metadataData []byte) (*Metadata, error) { - var ml MetadataLegacy d := yaml.NewDecoder(bytes.NewReader(metadataData)) // NOTE: No strict unmarshalling for legacy plugins - maintain backwards compatibility @@ -64,7 +63,6 @@ func loadMetadataLegacy(metadataData []byte) (*Metadata, error) { } func loadMetadataV1(metadataData []byte) (*Metadata, error) { - var mv1 MetadataV1 d := yaml.NewDecoder(bytes.NewReader(metadataData)) d.KnownFields(true) @@ -108,7 +106,6 @@ type prototypePluginManager struct { } func newPrototypePluginManager() (*prototypePluginManager, error) { - cc, err := wazero.NewCompilationCacheWithDir(helmpath.CachePath("wazero-build")) if err != nil { return nil, fmt.Errorf("failed to create wazero compilation cache: %w", err) @@ -212,7 +209,6 @@ func findPlugins(pluginsDirs []string, findFn findFunc, filterFn filterFunc) ([] found = append(found, p) } } - } return found, nil @@ -225,7 +221,6 @@ func makeDescriptorFilter(descriptor Descriptor) filterFunc { // If name is specified, it must match if descriptor.Name != "" && p.Metadata().Name != descriptor.Name { return false - } // If type is specified, it must match if descriptor.Type != "" && p.Metadata().Type != descriptor.Type { diff --git a/internal/plugin/loader_test.go b/internal/plugin/loader_test.go index 03ef02c85..c8531a490 100644 --- a/internal/plugin/loader_test.go +++ b/internal/plugin/loader_test.go @@ -62,7 +62,6 @@ name: "test-plugin" } func TestLoadDir(t *testing.T) { - makeMetadata := func(apiVersion string) Metadata { usage := "hello [params]..." if apiVersion == "legacy" { diff --git a/internal/plugin/metadata_test.go b/internal/plugin/metadata_test.go index 145ef5101..f59b6a78d 100644 --- a/internal/plugin/metadata_test.go +++ b/internal/plugin/metadata_test.go @@ -21,7 +21,6 @@ import ( ) func TestValidatePluginData(t *testing.T) { - // A mock plugin with no commands mockNoCommand := mockSubprocessCLIPlugin(t, "foo") mockNoCommand.metadata.RuntimeConfig = &RuntimeConfigSubprocess{ diff --git a/internal/plugin/plugin_type_registry_test.go b/internal/plugin/plugin_type_registry_test.go index 22f26262d..157c2f1d7 100644 --- a/internal/plugin/plugin_type_registry_test.go +++ b/internal/plugin/plugin_type_registry_test.go @@ -28,7 +28,6 @@ func TestMakeOutputMessage(t *testing.T) { ptm := pluginTypesIndex["getter/v1"] outputType := reflect.Zero(ptm.outputType).Interface() assert.IsType(t, schema.OutputMessageGetterV1{}, outputType) - } func TestMakeConfig(t *testing.T) { diff --git a/internal/plugin/runtime_extismv1.go b/internal/plugin/runtime_extismv1.go index cd9a02535..ffa108a08 100644 --- a/internal/plugin/runtime_extismv1.go +++ b/internal/plugin/runtime_extismv1.go @@ -99,7 +99,6 @@ type RuntimeExtismV1 struct { var _ Runtime = (*RuntimeExtismV1)(nil) func (r *RuntimeExtismV1) CreatePlugin(pluginDir string, metadata *Metadata) (Plugin, error) { - rc, ok := metadata.RuntimeConfig.(*RuntimeConfigExtismV1) if !ok { return nil, fmt.Errorf("invalid extism/v1 plugin runtime config type: %T", metadata.RuntimeConfig) @@ -139,7 +138,6 @@ func (p *ExtismV1PluginRuntime) Dir() string { } func (p *ExtismV1PluginRuntime) Invoke(ctx context.Context, input *Input) (*Output, error) { - var tmpDir string if p.rc.FileSystem.CreateTempDir { tmpDirInner, err := os.MkdirTemp(os.TempDir(), "helm-plugin-*") diff --git a/internal/plugin/subprocess_commands_test.go b/internal/plugin/subprocess_commands_test.go index df854b4ca..dbce42f6a 100644 --- a/internal/plugin/subprocess_commands_test.go +++ b/internal/plugin/subprocess_commands_test.go @@ -48,7 +48,6 @@ func TestPrepareCommand(t *testing.T) { } func TestPrepareCommandExtraArgs(t *testing.T) { - cmdMain := "sh" cmdArgs := []string{"-c", "echo \"test\""} platformCommand := []PlatformCommand{ diff --git a/internal/release/v2/util/manifest_sorter_test.go b/internal/release/v2/util/manifest_sorter_test.go index 28f0b34cc..c8851d678 100644 --- a/internal/release/v2/util/manifest_sorter_test.go +++ b/internal/release/v2/util/manifest_sorter_test.go @@ -26,7 +26,6 @@ import ( ) func TestSortManifests(t *testing.T) { - data := []struct { name []string path string @@ -183,7 +182,6 @@ metadata: if !reflect.DeepEqual(expectedHooks, out.Events) { t.Errorf("expected events: %v but got: %v", expectedHooks, out.Events) } - } } if !found { diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index 184c8404b..5f0c5b148 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -54,7 +54,6 @@ func New(chartpath, cachepath string, registryClient *registry.Client) *Resolver // Resolve resolves dependencies and returns a lock file with the resolution. func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) { - // Now we clone the dependencies, locking as we go. locked := make([]*chart.Dependency, len(reqs)) missing := []string{} @@ -146,7 +145,6 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string Version: version, }, }} - } else { // Retrieve list of tags for repository ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, registry.OCIScheme+"://"), d.Name) diff --git a/internal/third_party/dep/fs/fs_test.go b/internal/third_party/dep/fs/fs_test.go index 8f28c3af7..806e33c28 100644 --- a/internal/third_party/dep/fs/fs_test.go +++ b/internal/third_party/dep/fs/fs_test.go @@ -238,7 +238,6 @@ func TestCopyDirFail_SrcIsNotDir(t *testing.T) { if !errors.Is(err, errSrcNotDir) { t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errSrcNotDir, srcdir, dstdir, err) } - } func TestCopyDirFail_DstExists(t *testing.T) { @@ -491,7 +490,6 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { } func TestIsDir(t *testing.T) { - var currentUID = os.Getuid() if currentUID == 0 { @@ -544,7 +542,6 @@ func TestIsDir(t *testing.T) { } func TestIsSymlink(t *testing.T) { - var currentUID = os.Getuid() if currentUID == 0 { diff --git a/internal/version/version.go b/internal/version/version.go index 3daf80893..f37a97425 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -77,7 +77,6 @@ func GetUserAgent() string { // Get returns build info func Get() BuildInfo { - makeKubeClientVersionString := func() string { // Test builds don't include debug info / module info // (And even if they did, we probably want a stable version during tests anyway) diff --git a/pkg/action/action.go b/pkg/action/action.go index 75b6cf8a0..550d113da 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -521,7 +521,6 @@ func GetVersionSet(client discovery.ServerResourcesInterface) (common.VersionSet var ok bool for _, r := range resources { for _, rl := range r.APIResources { - // A Kind at a GroupVersion can show up more than once. We only want // it displayed once in the final output. id = path.Join(r.GroupVersion, rl.Kind) diff --git a/pkg/action/dependency.go b/pkg/action/dependency.go index 5c87f7cba..8ceca1433 100644 --- a/pkg/action/dependency.go +++ b/pkg/action/dependency.go @@ -120,7 +120,6 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, p if r := statArchiveForStatus(archive, dep); r != "" { return r } - } // End unnecessary code. diff --git a/pkg/action/get_metadata.go b/pkg/action/get_metadata.go index 5312dac7f..7d5a3f5f9 100644 --- a/pkg/action/get_metadata.go +++ b/pkg/action/get_metadata.go @@ -119,7 +119,6 @@ func (m *Metadata) FormattedDepNames() string { continue } depsNames = append(depsNames, ac.Name()) - } sort.StringSlice(depsNames).Sort() diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index a4a8da7a6..9bd5ed788 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -35,7 +35,6 @@ import ( func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration, serverSideApply bool) error { - shutdown, err := cfg.execHookWithDelayedShutdown(rl, hook, waitStrategy, waitOptions, timeout, serverSideApply) if shutdown == nil { return err @@ -59,7 +58,6 @@ func shutdownNoOp() error { func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration, serverSideApply bool) (ExecuteShutdownFunc, error) { - executingHooks := []*release.Hook{} for _, h := range rl.Hooks { @@ -179,7 +177,6 @@ func (x hookByWeight) Less(i, j int) bool { // deleteHookByPolicy deletes a hook if the hook policy instructs it to func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy, waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error { - // Never delete CustomResourceDefinitions; this could cause lots of // cascading garbage collection. if h.Kind == "CustomResourceDefinition" { @@ -214,7 +211,6 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo // deleteHooksByPolicy deletes all hooks if the hook policy instructs it to func (cfg *Configuration) deleteHooksByPolicy(hooks []*release.Hook, policy release.HookDeletePolicy, waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error { - for _, h := range hooks { if err := cfg.deleteHookByPolicy(h, policy, waitStrategy, waitOptions, timeout); err != nil { return err diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index e4d2b7376..5e5c5b227 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -737,7 +737,6 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) { }) } func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { - is := assert.New(t) instAction := installAction(t) instAction.ReleaseName = "interrupted-release" @@ -767,7 +766,6 @@ func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background time.Sleep(10 * time.Second) // wait for goroutine to finish is.Equal(goroutines, instAction.getGoroutineCount()) - } func TestNameTemplate(t *testing.T) { testCases := []nameTemplateTestCase{ @@ -804,7 +802,6 @@ func TestNameTemplate(t *testing.T) { } for _, tc := range testCases { - n, err := TemplateName(tc.tpl) if err != nil { if tc.expectedErrorStr == "" { diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go index 2e1d4ff07..f8a55c0e0 100644 --- a/pkg/action/package_test.go +++ b/pkg/action/package_test.go @@ -150,7 +150,6 @@ func TestValidateVersion(t *testing.T) { if !errors.Is(err, tt.wantErr) { t.Errorf("Expected {%v}, got {%v}", tt.wantErr, err) } - } }) } diff --git a/pkg/action/resource_policy.go b/pkg/action/resource_policy.go index fcea98ad6..4f4e2ff55 100644 --- a/pkg/action/resource_policy.go +++ b/pkg/action/resource_policy.go @@ -40,7 +40,6 @@ func filterManifestsToKeep(manifests []releaseutil.Manifest) (keep, remaining [] if resourcePolicyType == kube.KeepPolicy { keep = append(keep, m) } - } return keep, remaining } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 0f360fe37..05ff171db 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -263,7 +263,6 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str return nil, nil, false, err } } - } // determine if values will be reused diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 393692976..48721c1ae 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -446,7 +446,6 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { } func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) { - is := assert.New(t) req := require.New(t) @@ -741,7 +740,6 @@ func TestGetUpgradeServerSideValue(t *testing.T) { assert.ErrorContains(t, err, tt.expectedErrorMsg) }) } - } func TestUpgradeRun_UnreachableKubeClient(t *testing.T) { diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go index fd59abcb1..2223f57d5 100644 --- a/pkg/action/validate_test.go +++ b/pkg/action/validate_test.go @@ -273,7 +273,6 @@ func TestValidateNameAndGenerateName(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - skip, err := validateNameAndGenerateName(tc.info) if tc.wantErr { diff --git a/pkg/chart/common/capabilities.go b/pkg/chart/common/capabilities.go index 20f4953cf..16910acaa 100644 --- a/pkg/chart/common/capabilities.go +++ b/pkg/chart/common/capabilities.go @@ -167,7 +167,6 @@ func makeDefaultCapabilities() (*Capabilities, error) { } func newCapabilities(kubeVersionMajor, kubeVersionMinor uint64) (*Capabilities, error) { - version := fmt.Sprintf("v%d.%d.0", kubeVersionMajor, kubeVersionMinor) return &Capabilities{ KubeVersion: KubeVersion{ diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index 1d0baa84d..8ccdcebcf 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -666,7 +666,6 @@ func TestMergeTables(t *testing.T) { } func TestCoalesceValuesWarnings(t *testing.T) { - c := withDeps(&chart.Chart{ Metadata: &chart.Metadata{Name: "level1"}, Values: map[string]any{ @@ -724,7 +723,6 @@ func TestCoalesceValuesWarnings(t *testing.T) { assert.Contains(t, warnings, "warning: skipped value for level1.level2.level3.boat: Not a table.") assert.Contains(t, warnings, "warning: destination for level1.level2.level3.spear.tip is a table. Ignoring non-table value (true)") assert.Contains(t, warnings, "warning: cannot overwrite table with non table for level1.level2.level3.spear.sail (map[cotton:true])") - } func TestConcatPrefix(t *testing.T) { diff --git a/pkg/chart/common/util/values_test.go b/pkg/chart/common/util/values_test.go index 1dea04132..899ce3571 100644 --- a/pkg/chart/common/util/values_test.go +++ b/pkg/chart/common/util/values_test.go @@ -25,7 +25,6 @@ import ( ) func TestToRenderValues(t *testing.T) { - chartValues := map[string]any{ "name": "al Rashid", "where": map[string]any{ diff --git a/pkg/chart/loader/load.go b/pkg/chart/loader/load.go index 3fd381825..6fe246474 100644 --- a/pkg/chart/loader/load.go +++ b/pkg/chart/loader/load.go @@ -100,7 +100,6 @@ func LoadDir(dir string) (chart.Charter, error) { default: return nil, errors.New("unsupported chart version") } - } // FileLoader loads a chart from a file diff --git a/pkg/chart/v2/lint/lint.go b/pkg/chart/v2/lint/lint.go index 7f6f26320..23a93c551 100644 --- a/pkg/chart/v2/lint/lint.go +++ b/pkg/chart/v2/lint/lint.go @@ -44,7 +44,6 @@ func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption { } func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter { - chartDir, _ := filepath.Abs(baseDir) lo := linterOptions{} diff --git a/pkg/chart/v2/lint/rules/template.go b/pkg/chart/v2/lint/rules/template.go index 43665aa3a..ac5f740a0 100644 --- a/pkg/chart/v2/lint/rules/template.go +++ b/pkg/chart/v2/lint/rules/template.go @@ -62,7 +62,6 @@ func TemplateLinterSkipSchemaValidation(skipSchemaValidation bool) TemplateLinte } func newTemplateLinter(linter *support.Linter, namespace string, values map[string]any, options ...TemplateLinterOption) templateLinter { - result := templateLinter{ linter: linter, values: values, diff --git a/pkg/chart/v2/lint/rules/template_test.go b/pkg/chart/v2/lint/rules/template_test.go index b6a62e8fd..f0eb008b3 100644 --- a/pkg/chart/v2/lint/rules/template_test.go +++ b/pkg/chart/v2/lint/rules/template_test.go @@ -257,7 +257,6 @@ data: // // See https://github.com/helm/helm/issues/7483 func TestStrictTemplateParsingMapError(t *testing.T) { - ch := chart.Chart{ Metadata: &chart.Metadata{ Name: "regression7483", @@ -390,7 +389,6 @@ func TestValidateTopIndentLevel(t *testing.T) { t.Errorf("Expected %t for %q", shouldFail, doc) } } - } // TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments @@ -486,5 +484,4 @@ func TestIsYamlFileExtension(t *testing.T) { t.Errorf("isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected) } } - } diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index dad988605..e14b68dc7 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -390,7 +390,6 @@ icon: https://example.com/64x64.png if text.String() != "" { t.Errorf("Expected no message to Stderr, got %s", text.String()) } - } // Packaging the chart on a Windows machine will produce an @@ -660,7 +659,6 @@ func verifyChart(t *testing.T, c *chart.Chart) { t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version) } } - } func verifyDependencies(t *testing.T, c *chart.Chart) { diff --git a/pkg/chart/v2/util/create.go b/pkg/chart/v2/util/create.go index 0d7ae8d5c..36e3bfe72 100644 --- a/pkg/chart/v2/util/create.go +++ b/pkg/chart/v2/util/create.go @@ -701,7 +701,6 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error { // error. In such a case, this will attempt to clean up by removing the // new chart directory. func Create(name, dir string) (string, error) { - // Sanity-check the name of a chart so user doesn't create one that causes problems. if err := validateChartName(name); err != nil { return "", err diff --git a/pkg/chart/v2/util/dependencies_test.go b/pkg/chart/v2/util/dependencies_test.go index 0e4df8528..90a8806ec 100644 --- a/pkg/chart/v2/util/dependencies_test.go +++ b/pkg/chart/v2/util/dependencies_test.go @@ -459,7 +459,6 @@ func TestDependentChartAliases(t *testing.T) { if aliasChart := getAliasDependency(c.Dependencies(), req[2]); aliasChart != nil { t.Fatalf("expected no chart but got %s", aliasChart.Name()) } - } func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) { diff --git a/pkg/chart/v2/util/validate_name.go b/pkg/chart/v2/util/validate_name.go index 6595e085d..7d85a5b88 100644 --- a/pkg/chart/v2/util/validate_name.go +++ b/pkg/chart/v2/util/validate_name.go @@ -79,7 +79,6 @@ func ValidateReleaseName(name string) error { // This case is preserved for backwards compatibility if name == "" { return errMissingName - } if len(name) > maxReleaseNameLen || !validName.MatchString(name) { return errInvalidName diff --git a/pkg/cmd/completion_test.go b/pkg/cmd/completion_test.go index 81c1ee2ad..399ff1f0c 100644 --- a/pkg/cmd/completion_test.go +++ b/pkg/cmd/completion_test.go @@ -51,7 +51,6 @@ func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) { if shouldBePerformed { t.Errorf("Unexpected directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName) } else { - t.Errorf("Did not receive directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName) } t.Log(out) diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index 5a220d1ce..e7db3dde2 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -220,7 +220,6 @@ func (p *postRendererArgsSlice) Type() string { } func (p *postRendererArgsSlice) Set(val string) error { - // a post-renderer defined by a user may accept empty arguments p.options.args = append(p.options.args, val) diff --git a/pkg/cmd/get_metadata.go b/pkg/cmd/get_metadata.go index eb90b6e44..c26af646f 100644 --- a/pkg/cmd/get_metadata.go +++ b/pkg/cmd/get_metadata.go @@ -77,7 +77,6 @@ func newGetMetadataCmd(cfg *action.Configuration, out io.Writer) *cobra.Command } func (w metadataWriter) WriteTable(out io.Writer) error { - formatApplyMethod := func(applyMethod string) string { switch applyMethod { case "": diff --git a/pkg/cmd/helpers.go b/pkg/cmd/helpers.go index e555dd18b..8866b8650 100644 --- a/pkg/cmd/helpers.go +++ b/pkg/cmd/helpers.go @@ -43,7 +43,6 @@ func addDryRunFlag(cmd *cobra.Command) { // Determine the `action.DryRunStrategy` given -dry-run=` flag (or absence of) // Legacy usage of the flag: boolean values, and `--dry-run` (without value) are supported, and log warnings emitted func cmdGetDryRunFlagStrategy(cmd *cobra.Command, isTemplate bool) (action.DryRunStrategy, error) { - f := cmd.Flag("dry-run") v := f.Value.String() diff --git a/pkg/cmd/helpers_test.go b/pkg/cmd/helpers_test.go index 08065499e..8710085e6 100644 --- a/pkg/cmd/helpers_test.go +++ b/pkg/cmd/helpers_test.go @@ -157,7 +157,6 @@ func resetEnv() func() { } func TestCmdGetDryRunFlagStrategy(t *testing.T) { - type testCaseExpectedLog struct { Level string Msg string @@ -274,7 +273,6 @@ func TestCmdGetDryRunFlagStrategy(t *testing.T) { } for name, tc := range testCases { - logBuf := new(bytes.Buffer) logger := slog.New(slog.NewJSONHandler(logBuf, nil)) slog.SetDefault(logger) diff --git a/pkg/cmd/history.go b/pkg/cmd/history.go index 3349b7bc1..77b5bd9b7 100644 --- a/pkg/cmd/history.go +++ b/pkg/cmd/history.go @@ -224,7 +224,6 @@ func getReleaseHistory(rls []*release.Release) (history releaseHistory) { } if !r.Info.LastDeployed.IsZero() { rInfo.Updated = r.Info.LastDeployed - } history = append(history, rInfo) } diff --git a/pkg/cmd/lint_test.go b/pkg/cmd/lint_test.go index a13ec423b..ec6d18de8 100644 --- a/pkg/cmd/lint_test.go +++ b/pkg/cmd/lint_test.go @@ -60,7 +60,6 @@ func TestLintCmdWithQuietFlag(t *testing.T) { wantError: true, }} runTestCmd(t, tests) - } func TestLintCmdWithKubeVersionFlag(t *testing.T) { diff --git a/pkg/cmd/load_plugins.go b/pkg/cmd/load_plugins.go index 029dd04f5..b6cc38ce6 100644 --- a/pkg/cmd/load_plugins.go +++ b/pkg/cmd/load_plugins.go @@ -333,7 +333,6 @@ func loadFile(path string) (*pluginCommand, error) { // to obtain the dynamic completion choices. It must pass all the flags and sub-commands // specified in the command-line to the plugin.complete executable (except helm's global flags) func pluginDynamicComp(plug plugin.Plugin, cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - subprocessPlug, ok := plug.(*plugin.SubprocessPluginRuntime) if !ok { // Completion only supported for subprocess plugins (TODO: fix this) diff --git a/pkg/cmd/pull_test.go b/pkg/cmd/pull_test.go index f749c218c..511061dcd 100644 --- a/pkg/cmd/pull_test.go +++ b/pkg/cmd/pull_test.go @@ -260,7 +260,6 @@ func TestPullCmd(t *testing.T) { if out != outString { t.Errorf("%q: expected verification output %q, got %q", tt.name, outString, out) } - } ef := filepath.Join(outdir, tt.expectFile) diff --git a/pkg/cmd/repo_index_test.go b/pkg/cmd/repo_index_test.go index c8959f21e..68f105d6d 100644 --- a/pkg/cmd/repo_index_test.go +++ b/pkg/cmd/repo_index_test.go @@ -28,7 +28,6 @@ import ( ) func TestRepoIndexCmd(t *testing.T) { - dir := t.TempDir() comp := filepath.Join(dir, "compressedchart-0.1.0.tgz") diff --git a/pkg/cmd/repo_remove_test.go b/pkg/cmd/repo_remove_test.go index f2641ccf0..79778e75e 100644 --- a/pkg/cmd/repo_remove_test.go +++ b/pkg/cmd/repo_remove_test.go @@ -103,7 +103,6 @@ func TestRepoRemove(t *testing.T) { cacheIndex, cacheChart := createCacheFiles(rootDir, repoName) cacheFiles[repoName] = []string{cacheIndex, cacheChart} - } // Create repo remove command diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 04ba91c1f..1fa01ca0e 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -355,7 +355,6 @@ func hookOutputWriter(_, _, _ string) io.Writer { } func checkForExpiredRepos(repofile string) { - expiredRepos := []struct { name string old string @@ -399,7 +398,6 @@ func checkForExpiredRepos(repofile string) { ) } } - } func newRegistryClient( diff --git a/pkg/cmd/search.go b/pkg/cmd/search.go index 4d110286d..eb6c6e0b5 100644 --- a/pkg/cmd/search.go +++ b/pkg/cmd/search.go @@ -29,7 +29,6 @@ Use search subcommands to search different locations for charts. ` func newSearchCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ Use: "search [keyword]", Short: "search for a keyword in charts", diff --git a/pkg/cmd/search/search.go b/pkg/cmd/search/search.go index 1c7bb1d06..cb630709f 100644 --- a/pkg/cmd/search/search.go +++ b/pkg/cmd/search/search.go @@ -123,7 +123,6 @@ func (i *Index) Search(term string, threshold int, regexp bool) ([]*Result, erro // calcScore calculates a score for a match. func (i *Index) calcScore(index int, matchline string) int { - // This is currently tied to the fact that sep is a single char. splits := []int{} s := rune(sep[0]) diff --git a/pkg/cmd/search/search_test.go b/pkg/cmd/search/search_test.go index b3220394f..46394e77f 100644 --- a/pkg/cmd/search/search_test.go +++ b/pkg/cmd/search/search_test.go @@ -149,7 +149,6 @@ func TestAddRepo_Sort(t *testing.T) { } func TestSearchByName(t *testing.T) { - tests := []struct { name string query string @@ -245,7 +244,6 @@ func TestSearchByName(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - charts, err := i.Search(tt.query, 100, tt.regexp) if err != nil { if tt.fail { @@ -274,7 +272,6 @@ func TestSearchByName(t *testing.T) { t.Errorf("[%d]: Expected name %q, got %q", i, ex.Name, got.Name) } } - }) } } diff --git a/pkg/cmd/search_hub.go b/pkg/cmd/search_hub.go index f9adb73f4..3081961be 100644 --- a/pkg/cmd/search_hub.go +++ b/pkg/cmd/search_hub.go @@ -196,5 +196,4 @@ func (h *hubSearchWriter) encodeByFormat(out io.Writer, format output.Format) er // WriteJSON and WriteYAML, we shouldn't get invalid types return nil } - } diff --git a/pkg/cmd/search_hub_test.go b/pkg/cmd/search_hub_test.go index 8e056f771..4826181d2 100644 --- a/pkg/cmd/search_hub_test.go +++ b/pkg/cmd/search_hub_test.go @@ -24,7 +24,6 @@ import ( ) func TestSearchHubCmd(t *testing.T) { - // Setup a mock search service var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { @@ -54,7 +53,6 @@ func TestSearchHubCmd(t *testing.T) { } func TestSearchHubListRepoCmd(t *testing.T) { - // Setup a mock search service var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index 047fd60df..e5789db8b 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -142,7 +142,6 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return err } } - } } diff --git a/pkg/cmd/uninstall.go b/pkg/cmd/uninstall.go index 49f7bd19d..bfecd4741 100644 --- a/pkg/cmd/uninstall.go +++ b/pkg/cmd/uninstall.go @@ -59,7 +59,6 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return validationErr } for i := range args { - res, err := client.Run(args[i]) if err != nil { return err diff --git a/pkg/cmd/upgrade_test.go b/pkg/cmd/upgrade_test.go index f96f6ec0d..30188d3d9 100644 --- a/pkg/cmd/upgrade_test.go +++ b/pkg/cmd/upgrade_test.go @@ -34,7 +34,6 @@ import ( ) func TestUpgradeCmd(t *testing.T) { - tmpChart := t.TempDir() cfile := &chart.Chart{ Metadata: &chart.Metadata{ @@ -222,7 +221,6 @@ func TestUpgradeWithValue(t *testing.T) { if !strings.Contains(updatedRel.Manifest, "drink: tea") { t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest) } - } func TestUpgradeWithStringValue(t *testing.T) { @@ -253,11 +251,9 @@ func TestUpgradeWithStringValue(t *testing.T) { if !strings.Contains(updatedRel.Manifest, "drink: coffee") { t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest) } - } func TestUpgradeInstallWithSubchartNotes(t *testing.T) { - releaseName := "wacky-bunny-v1" relMock, ch, _ := prepareMockRelease(t, releaseName) @@ -289,11 +285,9 @@ func TestUpgradeInstallWithSubchartNotes(t *testing.T) { if !strings.Contains(upgradedRel.Info.Notes, "SUBCHART NOTES") { t.Errorf("The subchart notes are not set correctly. NOTES: %s", upgradedRel.Info.Notes) } - } func TestUpgradeWithValuesFile(t *testing.T) { - releaseName := "funny-bunny-v4" relMock, ch, chartPath := prepareMockRelease(t, releaseName) @@ -321,11 +315,9 @@ func TestUpgradeWithValuesFile(t *testing.T) { if !strings.Contains(updatedRel.Manifest, "drink: beer") { t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest) } - } func TestUpgradeWithValuesFromStdin(t *testing.T) { - releaseName := "funny-bunny-v5" relMock, ch, chartPath := prepareMockRelease(t, releaseName) @@ -361,7 +353,6 @@ func TestUpgradeWithValuesFromStdin(t *testing.T) { } func TestUpgradeInstallWithValuesFromStdin(t *testing.T) { - releaseName := "funny-bunny-v6" _, _, chartPath := prepareMockRelease(t, releaseName) @@ -392,7 +383,6 @@ func TestUpgradeInstallWithValuesFromStdin(t *testing.T) { if !strings.Contains(updatedRel.Manifest, "drink: beer") { t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest) } - } func prepareMockRelease(t *testing.T, releaseName string) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) { diff --git a/pkg/cmd/verify_test.go b/pkg/cmd/verify_test.go index ae373afd2..050d799c9 100644 --- a/pkg/cmd/verify_test.go +++ b/pkg/cmd/verify_test.go @@ -22,7 +22,6 @@ import ( ) func TestVerifyCmd(t *testing.T) { - statExe := "stat" statPathMsg := "no such file or directory" statFileMsg := statPathMsg diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 9c26f925e..22c6c71a3 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -282,7 +282,6 @@ func (c *ChartDownloader) DownloadToCache(ref, version string) (string, *provena // If provenance is requested, verify it. ver := &provenance.Verification{} if c.Verify > VerifyNever { - ppth, err := c.Cache.Get(digest32, CacheProv) if err == nil { slog.Debug("found provenance in cache", "id", digestString) @@ -308,7 +307,6 @@ func (c *ChartDownloader) DownloadToCache(ref, version string) (string, *provena } if c.Verify != VerifyLater { - // provenance files pin to a specific name so this needs to be accounted for // when verifying. // Note, this does make an assumption that the name/version is unique to a diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index fd4815cc4..3a999738e 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -502,11 +502,9 @@ Loop: // in a known repo and attempt to ensure the data is present for steps like // version resolution. func (m *Manager) ensureMissingRepos(repoNames map[string]string, deps []*chart.Dependency) (map[string]string, error) { - var ru []*repo.Entry for _, dd := range deps { - // If the chart is in the local charts directory no repository needs // to be specified. if dd.Repository == "" { @@ -679,7 +677,6 @@ func dedupeRepos(repos []*repo.Entry) []*repo.Entry { } func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { - var wg sync.WaitGroup localRepos := dedupeRepos(repos) diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 09edc3337..8f8d4494c 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -657,7 +657,6 @@ func TestRenderDependency(t *testing.T) { if out["outerchart/templates/outer"] != expect { t.Errorf("Expected %q, got %q", expect, out["outer"]) } - } func TestRenderNestedValues(t *testing.T) { @@ -813,7 +812,6 @@ func TestRenderBuiltinValues(t *testing.T) { t.Errorf("Expected %q, got %q", expect, out[file]) } } - } func TestAlterFuncMap_include(t *testing.T) { @@ -1009,7 +1007,6 @@ func TestAlterFuncMap_tplinclude(t *testing.T) { if got := out["TplFunction/templates/base"]; got != expect { t.Errorf("Expected %q, got %q (%v)", expect, got, out) } - } func TestRenderRecursionLimit(t *testing.T) { @@ -1068,7 +1065,6 @@ func TestRenderRecursionLimit(t *testing.T) { if got := out["overlook/templates/quote"]; got != expect { t.Errorf("Expected %q, got %q (%v)", expect, got, out) } - } func TestRenderLoadTemplateForTplFromFile(t *testing.T) { @@ -1319,7 +1315,6 @@ func TestRenderTplMissingKeyString(t *testing.T) { if !strings.Contains(errTxt, "noSuchKey") { t.Errorf("Expected error to contain 'noSuchKey', got %s", errTxt) } - } func TestNestedHelpersProducesMultilineStacktrace(t *testing.T) { diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index 7d4581233..f3116d626 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -510,7 +510,6 @@ func TestDownloadInsecureSkipTLSVerify(t *testing.T) { if _, err = g.Get(u.String()); err != nil { t.Error(err) } - } func TestHTTPGetterTarDownload(t *testing.T) { diff --git a/pkg/getter/plugingetter.go b/pkg/getter/plugingetter.go index ef8b87503..c683a1090 100644 --- a/pkg/getter/plugingetter.go +++ b/pkg/getter/plugingetter.go @@ -41,7 +41,6 @@ func collectGetterPlugins(settings *cli.EnvSettings) (Providers, error) { env := plugin.FormatEnv(settings.EnvVars()) pluginConstructorBuilder := func(plg plugin.Plugin) Constructor { return func(option ...Option) (Getter, error) { - return &getterPlugin{ options: append([]Option{}, option...), plg: plg, diff --git a/pkg/helmpath/lazypath.go b/pkg/helmpath/lazypath.go index c1f868754..bf6d71aa7 100644 --- a/pkg/helmpath/lazypath.go +++ b/pkg/helmpath/lazypath.go @@ -38,7 +38,6 @@ const ( type lazypath string func (l lazypath) path(helmEnvVar, xdgEnvVar string, defaultFn func() string, elem ...string) string { - // There is an order to checking for a path. // 1. See if a Helm specific environment variable has been set. // 2. Check if an XDG environment variable is set diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 44f31cdbe..96722bcd0 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -521,7 +521,6 @@ func determineFieldValidationDirective(validate bool) FieldValidationDirective { } func buildResourceList(f Factory, namespace string, validationDirective FieldValidationDirective, reader io.Reader, transformRequest resource.RequestTransform) (ResourceList, error) { - schema, err := f.Validator(string(validationDirective)) if err != nil { return nil, err @@ -859,7 +858,6 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate slog.String("fieldValidationDirective", string(updateOptions.fieldValidationDirective)), slog.Bool("upgradeClientSideFieldManager", updateOptions.upgradeClientSideFieldManager)) return func(original, target *resource.Info) error { - logger := c.Logger().With( slog.String("namespace", target.Namespace), slog.String("name", target.Name), @@ -954,7 +952,6 @@ func isIncompatibleServerError(err error) bool { // getManagedFieldsManager returns the manager string. If one was set it will be returned. // Otherwise, one is calculated based on the name of the binary. func getManagedFieldsManager() string { - // When a manager is explicitly set use it if ManagedFieldsManager != "" { return ManagedFieldsManager @@ -1102,7 +1099,6 @@ func createPatch(original runtime.Object, target *resource.Info, threeWayMergeFo } func replaceResource(target *resource.Info, fieldValidationDirective FieldValidationDirective) error { - helper := resource.NewHelper(target.Client, target.Mapping). WithFieldValidation(string(fieldValidationDirective)). WithFieldManager(getManagedFieldsManager()) @@ -1117,11 +1113,9 @@ func replaceResource(target *resource.Info, fieldValidationDirective FieldValida } return nil - } func patchResourceClientSide(original runtime.Object, target *resource.Info, threeWayMergeForUnstructured bool) error { - patch, patchType, err := createPatch(original, target, threeWayMergeForUnstructured) if err != nil { return fmt.Errorf("failed to create patch: %w", err) @@ -1155,14 +1149,12 @@ func patchResourceClientSide(original runtime.Object, target *resource.Info, thr // that upgrade CSA managed fields to SSA apply // see: https://github.com/kubernetes/kubernetes/pull/112905 func upgradeClientSideFieldManager(info *resource.Info, dryRun bool, fieldValidationDirective FieldValidationDirective) (bool, error) { - fieldManagerName := getManagedFieldsManager() patched := false err := retry.RetryOnConflict( retry.DefaultRetry, func() error { - if err := info.Get(); err != nil { return fmt.Errorf("failed to get object %s/%s %s: %w", info.Namespace, info.Name, info.Mapping.GroupVersionKind.String(), err) } diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 31894f68e..9b47a2df2 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -282,7 +282,6 @@ func TestCreate(t *testing.T) { c := newTestClient(t) for name, tc := range testCases { t.Run(name, func(t *testing.T) { - client := NewRequestResponseLogClient(t, func(previous []RequestResponseAction, req *http.Request) (*http.Response, error) { return tc.Callback(t, tc, previous, req) }) @@ -317,7 +316,6 @@ func TestCreate(t *testing.T) { } assert.Equal(t, tc.ExpectedActions, actions) - }) } } @@ -444,7 +442,6 @@ func TestUpdate(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { - listOriginal := tc.OriginalPods listTarget := tc.TargetPods @@ -1409,7 +1406,6 @@ func TestIsReachable(t *testing.T) { if !strings.Contains(err.Error(), tt.errorContains) { t.Errorf("expected error message to contain '%s', got: %v", tt.errorContains, err) } - } else { if err != nil { t.Errorf("expected no error but got: %v", err) @@ -1487,7 +1483,6 @@ func TestReplaceResource(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { - testFactory := cmdtesting.NewTestFactory() t.Cleanup(testFactory.Cleanup) @@ -1610,7 +1605,6 @@ func TestPatchResourceClientSide(t *testing.T) { t.Fail() return nil, nil - }, ExpectedErrorContains: "cannot patch \"whale\" with kind Pod: the server reported a conflict", }, @@ -1632,14 +1626,12 @@ func TestPatchResourceClientSide(t *testing.T) { t.Fail() return nil, nil // newResponse(http.StatusOK, &tc.TargetPods.Items[0]) - }, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { - testFactory := cmdtesting.NewTestFactory() t.Cleanup(testFactory.Cleanup) @@ -1806,7 +1798,6 @@ func TestPatchResourceServerSide(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { - testFactory := cmdtesting.NewTestFactory() t.Cleanup(testFactory.Cleanup) @@ -1837,7 +1828,6 @@ func TestPatchResourceServerSide(t *testing.T) { } func TestDetermineFieldValidationDirective(t *testing.T) { - assert.Equal(t, FieldValidationDirectiveIgnore, determineFieldValidationDirective(false)) assert.Equal(t, FieldValidationDirectiveStrict, determineFieldValidationDirective(true)) } diff --git a/pkg/registry/chart.go b/pkg/registry/chart.go index b00fc616d..d70b7e912 100644 --- a/pkg/registry/chart.go +++ b/pkg/registry/chart.go @@ -43,14 +43,12 @@ func extractChartMeta(chartData []byte) (*chart.Metadata, error) { // generateOCIAnnotations will generate OCI annotations to include within the OCI manifest func generateOCIAnnotations(meta *chart.Metadata, creationTime string) map[string]string { - // Get annotations from Chart attributes ociAnnotations := generateChartOCIAnnotations(meta, creationTime) // Copy Chart annotations annotations: for chartAnnotationKey, chartAnnotationValue := range meta.Annotations { - // Avoid overriding key properties for _, immutableOciKey := range immutableOciAnnotations { if immutableOciKey == chartAnnotationKey { @@ -88,7 +86,6 @@ func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[ var maintainerSb strings.Builder for maintainerIdx, maintainer := range meta.Maintainers { - if len(maintainer.Name) > 0 { maintainerSb.WriteString(maintainer.Name) } @@ -102,11 +99,9 @@ func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[ if maintainerIdx < len(meta.Maintainers)-1 { maintainerSb.WriteString(", ") } - } chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationAuthors, maintainerSb.String()) - } return chartOCIAnnotations @@ -114,7 +109,6 @@ func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[ // addToMap takes an existing map and adds an item if the value is not empty func addToMap(inputMap map[string]string, newKey string, newValue string) map[string]string { - // Add item to map if its if len(strings.TrimSpace(newValue)) > 0 { inputMap[newKey] = newValue diff --git a/pkg/registry/chart_test.go b/pkg/registry/chart_test.go index 77ccdaab7..0b247601b 100644 --- a/pkg/registry/chart_test.go +++ b/pkg/registry/chart_test.go @@ -27,7 +27,6 @@ import ( ) func TestGenerateOCIChartAnnotations(t *testing.T) { - nowString := time.Now().Format(time.RFC3339) tests := []struct { @@ -147,18 +146,15 @@ func TestGenerateOCIChartAnnotations(t *testing.T) { } for _, tt := range tests { - result := generateChartOCIAnnotations(tt.chart, nowString) if !reflect.DeepEqual(tt.expect, result) { t.Errorf("%s: expected map %v, got %v", tt.name, tt.expect, result) } - } } func TestGenerateOCIAnnotations(t *testing.T) { - nowString := time.Now().Format(time.RFC3339) tests := []struct { @@ -221,18 +217,15 @@ func TestGenerateOCIAnnotations(t *testing.T) { } for _, tt := range tests { - result := generateOCIAnnotations(tt.chart, nowString) if !reflect.DeepEqual(tt.expect, result) { t.Errorf("%s: expected map %v, got %v", tt.name, tt.expect, result) } - } } func TestGenerateOCICreatedAnnotations(t *testing.T) { - nowTime := time.Now() nowTimeString := nowTime.Format(time.RFC3339) @@ -268,7 +261,5 @@ func TestGenerateOCICreatedAnnotations(t *testing.T) { if !nowTime.Before(createdTimeAnnotation) { t.Errorf("%s annotation with value '%s' not configured properly. Annotation value is not after %s", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated], nowTimeString) } - } - } diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 62e560dba..2f3b344aa 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -816,7 +816,6 @@ func (c *Client) Tags(ref string) ([]string, error) { } return tags, nil - } // Resolve a reference to a descriptor. @@ -910,7 +909,6 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (string, *ur func (c *Client) tagManifest(ctx context.Context, memoryStore *memory.Store, configDescriptor ocispec.Descriptor, layers []ocispec.Descriptor, ociAnnotations map[string]string, parsedRef reference) (ocispec.Descriptor, error) { - manifest := ocispec.Manifest{ Versioned: specs.Versioned{SchemaVersion: 2}, Config: configDescriptor, diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index b0c08fa3c..1e944025f 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -380,7 +380,6 @@ func initFakeRegistryTestServer() string { } func testPush(suite *TestRegistry) { - testingChartCreationTime := "1977-09-02T22:04:05Z" // Bad bytes diff --git a/pkg/release/v1/util/manifest_sorter_test.go b/pkg/release/v1/util/manifest_sorter_test.go index 4360013e5..baa320be0 100644 --- a/pkg/release/v1/util/manifest_sorter_test.go +++ b/pkg/release/v1/util/manifest_sorter_test.go @@ -26,7 +26,6 @@ import ( ) func TestSortManifests(t *testing.T) { - data := []struct { name []string path string @@ -183,7 +182,6 @@ metadata: if !reflect.DeepEqual(expectedHooks, out.Events) { t.Errorf("expected events: %v but got: %v", expectedHooks, out.Events) } - } } if !found { diff --git a/pkg/repo/v1/chartrepo.go b/pkg/repo/v1/chartrepo.go index deef7474e..09b74d602 100644 --- a/pkg/repo/v1/chartrepo.go +++ b/pkg/repo/v1/chartrepo.go @@ -173,7 +173,6 @@ func WithInsecureSkipTLSVerify(insecureSkipTLSVerify bool) FindChartInRepoURLOpt // FindChartInRepoURL finds chart in chart repository pointed by repoURL // without adding repo to repositories func FindChartInRepoURL(repoURL string, chartName string, getters getter.Providers, options ...FindChartInRepoURLOption) (string, error) { - opts := findChartInRepoURLOptions{} for _, option := range options { option(&opts) diff --git a/pkg/repo/v1/chartrepo_test.go b/pkg/repo/v1/chartrepo_test.go index 7cffc04b6..fbd081e1b 100644 --- a/pkg/repo/v1/chartrepo_test.go +++ b/pkg/repo/v1/chartrepo_test.go @@ -126,7 +126,6 @@ func TestConcurrencyDownloadIndex(t *testing.T) { // 2) read index.yaml via LoadIndexFile (read operation). // This checks for race conditions and ensures correct behavior under concurrent read/write access. for range 150 { - wg.Go(func() { idx, err := repo.DownloadIndexFile() if err != nil { @@ -234,7 +233,6 @@ func TestFindChartInRepoURL(t *testing.T) { } func TestErrorFindChartInRepoURL(t *testing.T) { - g := getter.All(&cli.EnvSettings{ RepositoryCache: t.TempDir(), }) diff --git a/pkg/repo/v1/index_test.go b/pkg/repo/v1/index_test.go index 6c6bb7835..3cacc95d4 100644 --- a/pkg/repo/v1/index_test.go +++ b/pkg/repo/v1/index_test.go @@ -140,7 +140,6 @@ func TestIndexFile(t *testing.T) { } func TestLoadIndex(t *testing.T) { - tests := []struct { Name string Filename string @@ -250,7 +249,6 @@ func TestMerge(t *testing.T) { if v := vs[1]; v.Version != "0.2.0" { t.Errorf("Expected %q version to be 0.2.0, got %s", v.Name, v.Version) } - } func TestDownloadIndexFile(t *testing.T) { @@ -642,7 +640,6 @@ func TestIgnoreSkippableChartValidationError(t *testing.T) { if !errors.Is(tc.Input, result) { t.Error("expected the result equal to input") } - }) } } diff --git a/pkg/repo/v1/repotest/server_test.go b/pkg/repo/v1/repotest/server_test.go index f0e374fc0..499091a57 100644 --- a/pkg/repo/v1/repotest/server_test.go +++ b/pkg/repo/v1/repotest/server_test.go @@ -143,7 +143,6 @@ func TestNewTempServer(t *testing.T) { if res.StatusCode != http.StatusOK { t.Errorf("Expected 200, got %d", res.StatusCode) } - } { @@ -203,7 +202,6 @@ func TestNewTempServer(t *testing.T) { } }) } - } func TestNewTempServer_TLS(t *testing.T) { diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go index 329b82b2f..da3e02f42 100644 --- a/pkg/storage/driver/memory_test.go +++ b/pkg/storage/driver/memory_test.go @@ -300,5 +300,4 @@ func TestMemoryDelete(t *testing.T) { t.Logf("Name: %s, Version: %d", rac.Name(), rac.Version()) } } - } diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go index 21d9f6679..ea85756eb 100644 --- a/pkg/storage/driver/sql.go +++ b/pkg/storage/driver/sql.go @@ -134,7 +134,6 @@ func (s *SQL) checkAlreadyApplied(migrations []*migrate.Migration) bool { } func (s *SQL) ensureDBSetup() error { - migrations := &migrate.MemoryMigrationSource{ Migrations: []*migrate.Migration{ { diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index cecaa2453..1dbbd0e1b 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -330,7 +330,6 @@ func (t *parser) keyIndex() (int, error) { } // v should be the index return strconv.Atoi(string(v)) - } func (t *parser) listItem(list []any, i, nestedNameLevel int) ([]any, error) { From 62494896e9a105b63df2c76c638c53304a37121e Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Sat, 14 Mar 2026 08:50:41 +0100 Subject: [PATCH 228/571] chore: enable perfsprint linter Signed-off-by: Matthieu MOREL --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index 536b4b212..93349100a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -29,6 +29,7 @@ linters: - modernize - nakedret - nolintlint + - perfsprint - revive - sloglint - staticcheck From af31a679244b584317fd8053aee498c4d5ec301c Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Sat, 14 Mar 2026 09:06:55 +0100 Subject: [PATCH 229/571] chore: enable errorlint linter Signed-off-by: Matthieu MOREL --- .golangci.yml | 7 +++++++ pkg/action/validate.go | 14 +++++++------- pkg/cmd/lint.go | 2 +- pkg/cmd/plugin_uninstall.go | 2 +- pkg/cmd/plugin_update.go | 2 +- pkg/downloader/manager.go | 8 ++++---- pkg/kube/client.go | 4 ++-- pkg/storage/driver/sql.go | 4 ++-- 8 files changed, 25 insertions(+), 18 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 536b4b212..f6fd1b02f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -21,6 +21,7 @@ linters: enable: - depguard - dupl + - errorlint - exhaustive - gomodguard - govet @@ -63,6 +64,12 @@ linters: dupl: threshold: 400 + errorlint: + # Check for plain type assertions and type switches. + asserts: false + # Check for plain error comparisons. + comparison: false + exhaustive: default-signifies-exhaustive: true diff --git a/pkg/action/validate.go b/pkg/action/validate.go index 94bf4906b..8a127a512 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -93,7 +93,7 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN // Allow adoption of the resource if it is managed by Helm and is annotated with correct release name and namespace. if err := checkOwnership(existing, releaseName, releaseNamespace); err != nil { - return fmt.Errorf("%s exists and cannot be imported into the current release: %s", resourceString(info), err) + return fmt.Errorf("%s exists and cannot be imported into the current release: %w", resourceString(info), err) } infoCopy := *info @@ -116,13 +116,13 @@ func checkOwnership(obj runtime.Object, releaseName, releaseNamespace string) er var errs []error if err := requireValue(lbls, appManagedByLabel, appManagedByHelm); err != nil { - errs = append(errs, fmt.Errorf("label validation error: %s", err)) + errs = append(errs, fmt.Errorf("label validation error: %w", err)) } if err := requireValue(annos, helmReleaseNameAnnotation, releaseName); err != nil { - errs = append(errs, fmt.Errorf("annotation validation error: %s", err)) + errs = append(errs, fmt.Errorf("annotation validation error: %w", err)) } if err := requireValue(annos, helmReleaseNamespaceAnnotation, releaseNamespace); err != nil { - errs = append(errs, fmt.Errorf("annotation validation error: %s", err)) + errs = append(errs, fmt.Errorf("annotation validation error: %w", err)) } if len(errs) > 0 { @@ -154,7 +154,7 @@ func setMetadataVisitor(releaseName, releaseNamespace string, forceOwnership boo if !forceOwnership { if err := checkOwnership(info.Object, releaseName, releaseNamespace); err != nil { - return fmt.Errorf("%s cannot be owned: %s", resourceString(info), err) + return fmt.Errorf("%s cannot be owned: %w", resourceString(info), err) } } @@ -162,7 +162,7 @@ func setMetadataVisitor(releaseName, releaseNamespace string, forceOwnership boo appManagedByLabel: appManagedByHelm, }); err != nil { return fmt.Errorf( - "%s labels could not be updated: %s", + "%s labels could not be updated: %w", resourceString(info), err, ) } @@ -172,7 +172,7 @@ func setMetadataVisitor(releaseName, releaseNamespace string, forceOwnership boo helmReleaseNamespaceAnnotation: releaseNamespace, }); err != nil { return fmt.Errorf( - "%s annotations could not be updated: %s", + "%s annotations could not be updated: %w", resourceString(info), err, ) } diff --git a/pkg/cmd/lint.go b/pkg/cmd/lint.go index ccc53ddd0..1b5c3b212 100644 --- a/pkg/cmd/lint.go +++ b/pkg/cmd/lint.go @@ -59,7 +59,7 @@ func newLintCmd(out io.Writer) *cobra.Command { if kubeVersion != "" { parsedKubeVersion, err := common.ParseKubeVersion(kubeVersion) if err != nil { - return fmt.Errorf("invalid kube version '%s': %s", kubeVersion, err) + return fmt.Errorf("invalid kube version '%s': %w", kubeVersion, err) } client.KubeVersion = parsedKubeVersion } diff --git a/pkg/cmd/plugin_uninstall.go b/pkg/cmd/plugin_uninstall.go index 85eb46219..88c46fe85 100644 --- a/pkg/cmd/plugin_uninstall.go +++ b/pkg/cmd/plugin_uninstall.go @@ -70,7 +70,7 @@ func (o *pluginUninstallOptions) run(out io.Writer) error { for _, name := range o.names { if found := findPlugin(plugins, name); found != nil { if err := uninstallPlugin(found); err != nil { - errorPlugins = append(errorPlugins, fmt.Errorf("failed to uninstall plugin %s, got error (%v)", name, err)) + errorPlugins = append(errorPlugins, fmt.Errorf("failed to uninstall plugin %s, got error (%w)", name, err)) } else { fmt.Fprintf(out, "Uninstalled plugin: %s\n", name) } diff --git a/pkg/cmd/plugin_update.go b/pkg/cmd/plugin_update.go index 6cc2729fc..f7c2e5c2d 100644 --- a/pkg/cmd/plugin_update.go +++ b/pkg/cmd/plugin_update.go @@ -71,7 +71,7 @@ func (o *pluginUpdateOptions) run(out io.Writer) error { for _, name := range o.names { if found := findPlugin(plugins, name); found != nil { if err := updatePlugin(found); err != nil { - errorPlugins = append(errorPlugins, fmt.Errorf("failed to update plugin %s, got error (%v)", name, err)) + errorPlugins = append(errorPlugins, fmt.Errorf("failed to update plugin %s, got error (%w)", name, err)) } else { fmt.Fprintf(out, "Updated plugin: %s\n", name) } diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index fd4815cc4..39112ef03 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -261,7 +261,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { return err } } else { - return fmt.Errorf("unable to retrieve file info for '%s': %v", destPath, err) + return fmt.Errorf("unable to retrieve file info for '%s': %w", destPath, err) } // Prepare tmpPath @@ -281,17 +281,17 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { chartPath := filepath.Join(destPath, dep.Name) ch, err := loader.LoadDir(chartPath) if err != nil { - return fmt.Errorf("unable to load chart '%s': %v", chartPath, err) + return fmt.Errorf("unable to load chart '%s': %w", chartPath, err) } constraint, err := semver.NewConstraint(dep.Version) if err != nil { - return fmt.Errorf("dependency %s has an invalid version/constraint format: %s", dep.Name, err) + return fmt.Errorf("dependency %s has an invalid version/constraint format: %w", dep.Name, err) } v, err := semver.NewVersion(ch.Metadata.Version) if err != nil { - return fmt.Errorf("invalid version %s for dependency %s: %s", dep.Version, dep.Name, err) + return fmt.Errorf("invalid version %s for dependency %s: %w", dep.Version, dep.Name, err) } if !constraint.Check(v) { diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 44f31cdbe..81eae66e3 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -1234,7 +1234,7 @@ func patchResourceServerSide(target *resource.Info, dryRun bool, forceConflicts ) if err != nil { if isIncompatibleServerError(err) { - return fmt.Errorf("server-side apply not available on the server: %v", err) + return fmt.Errorf("server-side apply not available on the server: %w", err) } if apierrors.IsConflict(err) { @@ -1251,7 +1251,7 @@ func patchResourceServerSide(target *resource.Info, dryRun bool, forceConflicts func (c *Client) GetPodList(namespace string, listOptions metav1.ListOptions) (*v1.PodList, error) { podList, err := c.kubeClient.CoreV1().Pods(namespace).List(context.Background(), listOptions) if err != nil { - return nil, fmt.Errorf("failed to get pod list with options: %+v with error: %v", listOptions, err) + return nil, fmt.Errorf("failed to get pod list with options: %+v with error: %w", listOptions, err) } return podList, nil } diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go index 21d9f6679..f274be1fb 100644 --- a/pkg/storage/driver/sql.go +++ b/pkg/storage/driver/sql.go @@ -485,7 +485,7 @@ func (s *SQL) Create(key string, rel release.Releaser) error { transaction, err := s.db.Beginx() if err != nil { 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: %w", err) } insertQuery, args, err := s.statementBuilder. @@ -623,7 +623,7 @@ func (s *SQL) Delete(key string) (release.Releaser, error) { transaction, err := s.db.Beginx() if err != nil { 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: %w", err) } selectQuery, args, err := s.statementBuilder. From 83a0138172e22f5efb7d9852b3ed0fb5fc8117b2 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Sat, 14 Mar 2026 09:16:26 +0100 Subject: [PATCH 230/571] chore: fix deprecatedComment issues from gocritic Signed-off-by: Matthieu MOREL --- .golangci.yml | 43 ++++++++++++++++++++++++++++++ internal/chart/v3/chart.go | 1 + internal/plugin/metadata_legacy.go | 6 +++-- pkg/chart/v2/chart.go | 1 + 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 536b4b212..ccd721a00 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -22,6 +22,7 @@ linters: - depguard - dupl - exhaustive + - gocritic - gomodguard - govet - ineffassign @@ -66,6 +67,48 @@ linters: exhaustive: default-signifies-exhaustive: true + gocritic: + disabled-checks: + - appendAssign + - appendCombine + - assignOp + - badCall + - commentedOutCode + - commentFormatting + - deferInLoop + - elseif + - emptyStringTest + - exposedSyncMutex + - filepathJoin + - httpNoBody + - hugeParam + - ifElseChain + - importShadow + - nestingReduce + - nilValReturn + - octalLiteral + - paramTypeCombine + - preferStringWriter + - ptrToRefParam + - rangeValCopy + - regexpSimplify + - singleCaseSwitch + - sloppyReassign + - sloppyLen + - sprintfQuotedString + - stringConcatSimplify + - stringsCompare + - stringXbytes + - todoCommentWithoutDetail + - tooManyResultsChecker + - typeAssertChain + - uncheckedInlineErr + - unnamedResult + - unnecessaryDefer + - unslice + - whyNoLint + enable-all: true + gomodguard: blocked: modules: diff --git a/internal/chart/v3/chart.go b/internal/chart/v3/chart.go index d76a5688c..71a90c855 100644 --- a/internal/chart/v3/chart.go +++ b/internal/chart/v3/chart.go @@ -140,6 +140,7 @@ func (ch *Chart) AppVersion() string { } // CRDs returns a list of File objects in the 'crds/' directory of a Helm chart. +// // Deprecated: use CRDObjects() func (ch *Chart) CRDs() []*common.File { files := []*common.File{} diff --git a/internal/plugin/metadata_legacy.go b/internal/plugin/metadata_legacy.go index b5849edeb..47db9fe7f 100644 --- a/internal/plugin/metadata_legacy.go +++ b/internal/plugin/metadata_legacy.go @@ -50,7 +50,8 @@ type MetadataLegacy struct { PlatformCommand []PlatformCommand `yaml:"platformCommand"` // Command is the plugin command, as a single string. - // DEPRECATED: Use PlatformCommand instead. Removed in subprocess/v1 plugins. + // + // Deprecated: Use PlatformCommand instead. Removed in subprocess/v1 plugins. Command string `yaml:"command"` // IgnoreFlags ignores any flags passed in from Helm @@ -60,7 +61,8 @@ type MetadataLegacy struct { PlatformHooks PlatformHooks `yaml:"platformHooks"` // Hooks are commands that will run on plugin events, as a single string. - // DEPRECATED: Use PlatformHooks instead. Removed in subprocess/v1 plugins. + // + // Deprecated: Use PlatformHooks instead. Removed in subprocess/v1 plugins. Hooks Hooks `yaml:"hooks"` // Downloaders field is used if the plugin supply downloader mechanism diff --git a/pkg/chart/v2/chart.go b/pkg/chart/v2/chart.go index 4cfc2b890..9d798b4de 100644 --- a/pkg/chart/v2/chart.go +++ b/pkg/chart/v2/chart.go @@ -143,6 +143,7 @@ func (ch *Chart) AppVersion() string { } // CRDs returns a list of File objects in the 'crds/' directory of a Helm chart. +// // Deprecated: use CRDObjects() func (ch *Chart) CRDs() []*common.File { files := []*common.File{} From 071558d69ffbb408dcb56403d387a1aa90a7d3a8 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Sat, 14 Mar 2026 09:32:42 +0100 Subject: [PATCH 231/571] chore: fix bool-compare issues from testifylint Signed-off-by: Matthieu MOREL --- .golangci.yml | 19 +++++++++++++++++++ internal/chart/v3/chart_test.go | 4 ++-- pkg/action/get_values_test.go | 2 +- pkg/action/push_test.go | 4 ++-- pkg/action/registry_login_test.go | 4 ++-- pkg/chart/v2/chart_test.go | 4 ++-- 6 files changed, 28 insertions(+), 9 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 536b4b212..bbd7a18b8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -32,6 +32,7 @@ linters: - revive - sloglint - staticcheck + - testifylint - thelper - unused - usestdlibvars @@ -92,6 +93,24 @@ linters: - helpers - models + testifylint: + disable: + - empty + - encoded-compare + - equal-values + - error-is-as + - error-nil + - expected-actual + - float-compare + - go-require + - len + - nil-compare + - require-error + - suite-dont-use-pkg + - suite-extra-assert-call + # Intentionally enable all testifylint rules so new checks are adopted automatically. + enable-all: true + run: timeout: 10m diff --git a/internal/chart/v3/chart_test.go b/internal/chart/v3/chart_test.go index 07cbf4b39..5f6ca548d 100644 --- a/internal/chart/v3/chart_test.go +++ b/internal/chart/v3/chart_test.go @@ -124,8 +124,8 @@ func TestIsRoot(t *testing.T) { is := assert.New(t) - is.Equal(false, chrt1.IsRoot()) - is.Equal(true, chrt2.IsRoot()) + is.False(chrt1.IsRoot()) + is.True(chrt2.IsRoot()) } func TestChartPath(t *testing.T) { diff --git a/pkg/action/get_values_test.go b/pkg/action/get_values_test.go index 01ee4c3f1..c4cbdf2d5 100644 --- a/pkg/action/get_values_test.go +++ b/pkg/action/get_values_test.go @@ -37,7 +37,7 @@ func TestNewGetValues(t *testing.T) { assert.NotNil(t, client) assert.Equal(t, cfg, client.cfg) assert.Equal(t, 0, client.Version) - assert.Equal(t, false, client.AllValues) + assert.False(t, client.AllValues) } func TestGetValues_Run_UserConfigOnly(t *testing.T) { diff --git a/pkg/action/push_test.go b/pkg/action/push_test.go index 35c6f3efc..125799252 100644 --- a/pkg/action/push_test.go +++ b/pkg/action/push_test.go @@ -47,14 +47,14 @@ func TestNewPushWithInsecureSkipTLSVerify(t *testing.T) { client := NewPushWithOpts(WithInsecureSkipTLSVerify(true)) assert.NotNil(t, client) - assert.Equal(t, true, client.insecureSkipTLSVerify) + assert.True(t, client.insecureSkipTLSVerify) } func TestNewPushWithPlainHTTP(t *testing.T) { client := NewPushWithOpts(WithPlainHTTP(true)) assert.NotNil(t, client) - assert.Equal(t, true, client.plainHTTP) + assert.True(t, client.plainHTTP) } func TestNewPushWithPushOptWriter(t *testing.T) { diff --git a/pkg/action/registry_login_test.go b/pkg/action/registry_login_test.go index de2450d9d..590673b3a 100644 --- a/pkg/action/registry_login_test.go +++ b/pkg/action/registry_login_test.go @@ -48,7 +48,7 @@ func TestWithInsecure(t *testing.T) { opt := WithInsecure(true) assert.Nil(t, opt(client)) - assert.Equal(t, true, client.insecure) + assert.True(t, client.insecure) } func TestWithKeyFile(t *testing.T) { @@ -80,5 +80,5 @@ func TestWithPlainHTTPLogin(t *testing.T) { opt := WithPlainHTTPLogin(true) assert.Nil(t, opt(client)) - assert.Equal(t, true, client.plainHTTP) + assert.True(t, client.plainHTTP) } diff --git a/pkg/chart/v2/chart_test.go b/pkg/chart/v2/chart_test.go index d0837eb16..d44e7251b 100644 --- a/pkg/chart/v2/chart_test.go +++ b/pkg/chart/v2/chart_test.go @@ -124,8 +124,8 @@ func TestIsRoot(t *testing.T) { is := assert.New(t) - is.Equal(false, chrt1.IsRoot()) - is.Equal(true, chrt2.IsRoot()) + is.False(chrt1.IsRoot()) + is.True(chrt2.IsRoot()) } func TestChartPath(t *testing.T) { From 37185d2ea6a091b93d2e71cc55ab16b2c0f3f9e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 02:21:05 +0000 Subject: [PATCH 232/571] chore(deps): bump google.golang.org/grpc from 1.78.0 to 1.79.3 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.78.0 to 1.79.3. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.78.0...v1.79.3) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.79.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bdad47a64..09f903582 100644 --- a/go.mod +++ b/go.mod @@ -167,7 +167,7 @@ require ( golang.org/x/tools v0.41.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/grpc v1.78.0 // indirect + google.golang.org/grpc v1.79.3 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 2ec3731df..2505eea61 100644 --- a/go.sum +++ b/go.sum @@ -467,8 +467,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1: google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From 7edfff33ebcb0f5d961afec34393c222de92de12 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Sun, 22 Mar 2026 20:56:52 +0100 Subject: [PATCH 233/571] chore: fix unnecessary-format issues from revive Signed-off-by: Matthieu MOREL --- .github/env | 2 +- .golangci.yml | 4 +++- internal/chart/v3/lint/lint_test.go | 6 +++--- internal/chart/v3/lint/rules/chartfile_test.go | 12 ++++++------ internal/chart/v3/lint/rules/dependencies_test.go | 4 ++-- internal/chart/v3/lint/rules/deprecations_test.go | 2 +- internal/chart/v3/lint/rules/values_test.go | 2 +- internal/chart/v3/loader/load_test.go | 2 +- internal/chart/v3/util/chartfile_test.go | 6 +++--- internal/chart/v3/util/create_test.go | 2 +- internal/plugin/installer/http_installer_test.go | 10 +++++----- internal/plugin/installer/installer.go | 2 +- internal/plugin/installer/installer_test.go | 6 +++--- internal/plugin/installer/oci_installer_test.go | 14 +++++++------- internal/plugin/installer/verification_test.go | 6 +++--- pkg/action/action_test.go | 2 +- pkg/action/hooks_test.go | 4 ++-- pkg/action/install_test.go | 2 +- pkg/action/lint_test.go | 2 +- pkg/chart/common/capabilities_test.go | 2 +- pkg/chart/common/util/values_test.go | 2 +- pkg/chart/common/values_test.go | 10 +++++----- pkg/chart/v2/lint/lint_test.go | 6 +++--- pkg/chart/v2/lint/rules/chartfile_test.go | 12 ++++++------ pkg/chart/v2/lint/rules/dependencies_test.go | 4 ++-- pkg/chart/v2/lint/rules/deprecations_test.go | 2 +- pkg/chart/v2/lint/rules/values_test.go | 2 +- pkg/chart/v2/loader/load.go | 4 ++-- pkg/chart/v2/loader/load_test.go | 2 +- pkg/chart/v2/util/chartfile_test.go | 6 +++--- pkg/chart/v2/util/create_test.go | 2 +- pkg/cli/values/options_test.go | 2 +- pkg/cmd/completion.go | 2 +- pkg/cmd/plugin_install.go | 6 +++--- pkg/cmd/plugin_package.go | 2 +- pkg/cmd/repo_add_test.go | 4 ++-- pkg/cmd/rollback.go | 2 +- pkg/cmd/search_hub_test.go | 2 +- pkg/engine/engine_test.go | 6 +++--- pkg/kube/wait_test.go | 4 ++-- pkg/provenance/sign_test.go | 2 +- pkg/registry/client_test.go | 4 ++-- pkg/registry/plugin_test.go | 2 +- pkg/repo/v1/chartrepo_test.go | 12 ++++++------ pkg/repo/v1/index_test.go | 12 ++++++------ pkg/repo/v1/repo_test.go | 4 ++-- pkg/storage/driver/memory_test.go | 2 +- pkg/storage/storage_test.go | 2 +- 48 files changed, 108 insertions(+), 106 deletions(-) diff --git a/.github/env b/.github/env index a22fa80ca..fc6f21880 100644 --- a/.github/env +++ b/.github/env @@ -1,2 +1,2 @@ GOLANG_VERSION=1.25 -GOLANGCI_LINT_VERSION=v2.10.1 +GOLANGCI_LINT_VERSION=v2.11.3 diff --git a/.golangci.yml b/.golangci.yml index 536b4b212..954c93b83 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -77,9 +77,11 @@ linters: require-specific: true revive: - max-open-files: 2048 + enable-all-rules: false enable-default-rules: true + max-open-files: 2048 rules: + - name: unnecessary-format - name: var-naming arguments: - ["ID"] # AllowList diff --git a/internal/chart/v3/lint/lint_test.go b/internal/chart/v3/lint/lint_test.go index d8242dc95..afacb8052 100644 --- a/internal/chart/v3/lint/lint_test.go +++ b/internal/chart/v3/lint/lint_test.go @@ -95,7 +95,7 @@ func TestInvalidYaml(t *testing.T) { t.Fatalf("All didn't fail with expected errors, got %#v", m) } if !strings.Contains(m[0].Err.Error(), "deliberateSyntaxError") { - t.Errorf("All didn't have the error for deliberateSyntaxError") + t.Error("All didn't have the error for deliberateSyntaxError") } } @@ -107,7 +107,7 @@ func TestInvalidChartYamlV3(t *testing.T) { t.Fatalf("All didn't fail with expected errors, got %#v", m) } if !strings.Contains(m[0].Err.Error(), "failed to strictly parse chart metadata file") { - t.Errorf("All didn't have the error for duplicate YAML keys") + t.Error("All didn't have the error for duplicate YAML keys") } } @@ -237,7 +237,7 @@ func TestMalformedTemplate(t *testing.T) { t.Fatalf("All didn't fail with expected errors, got %#v", m) } if !strings.Contains(m[0].Err.Error(), "invalid character '{'") { - t.Errorf("All didn't have the error for invalid character '{'") + t.Error("All didn't have the error for invalid character '{'") } } } diff --git a/internal/chart/v3/lint/rules/chartfile_test.go b/internal/chart/v3/lint/rules/chartfile_test.go index 57893e151..a7669a0aa 100644 --- a/internal/chart/v3/lint/rules/chartfile_test.go +++ b/internal/chart/v3/lint/rules/chartfile_test.go @@ -50,26 +50,26 @@ func TestValidateChartYamlNotDirectory(t *testing.T) { err := validateChartYamlNotDirectory(nonExistingChartFilePath) if err == nil { - t.Errorf("validateChartYamlNotDirectory to return a linter error, got no error") + t.Error("validateChartYamlNotDirectory to return a linter error, got no error") } } func TestValidateChartYamlFormat(t *testing.T) { err := validateChartYamlFormat(errors.New("Read error")) if err == nil { - t.Errorf("validateChartYamlFormat to return a linter error, got no error") + t.Error("validateChartYamlFormat to return a linter error, got no error") } err = validateChartYamlFormat(nil) if err != nil { - t.Errorf("validateChartYamlFormat to return no error, got a linter error") + t.Error("validateChartYamlFormat to return no error, got a linter error") } } func TestValidateChartName(t *testing.T) { err := validateChartName(badChart) if err == nil { - t.Errorf("validateChartName to return a linter error, got no error") + t.Error("validateChartName to return a linter error, got no error") } err = validateChartName(badChartName) @@ -149,7 +149,7 @@ func TestValidateChartMaintainer(t *testing.T) { badChart.Maintainers = []*chart.Maintainer{nil} err := validateChartMaintainer(badChart) if err == nil { - t.Errorf("validateChartMaintainer did not return error for nil maintainer as expected") + t.Error("validateChartMaintainer did not return error for nil maintainer as expected") } if err.Error() != "a maintainer entry is empty" { t.Errorf("validateChartMaintainer returned unexpected error for nil maintainer: %s", err.Error()) @@ -185,7 +185,7 @@ func TestValidateChartIconPresence(t *testing.T) { err := validateChartIconPresence(testChart) if err == nil { - t.Errorf("validateChartIconPresence to return a linter error, got no error") + t.Error("validateChartIconPresence to return a linter error, got no error") } else if !strings.Contains(err.Error(), "icon is recommended") { t.Errorf("expected %q, got %q", "icon is recommended", err.Error()) } diff --git a/internal/chart/v3/lint/rules/dependencies_test.go b/internal/chart/v3/lint/rules/dependencies_test.go index b80e4b8a9..31fd0ef33 100644 --- a/internal/chart/v3/lint/rules/dependencies_test.go +++ b/internal/chart/v3/lint/rules/dependencies_test.go @@ -72,7 +72,7 @@ func TestValidateDependencyInMetadata(t *testing.T) { c := chartWithBadDependencies() if err := validateDependencyInMetadata(&c); err == nil { - t.Errorf("chart should have been flagged for missing deps in chart metadata") + t.Error("chart should have been flagged for missing deps in chart metadata") } } @@ -132,7 +132,7 @@ func TestValidateDependenciesUnique(t *testing.T) { for _, tt := range tests { if err := validateDependenciesUnique(&tt.chart); err == nil { - t.Errorf("chart should have been flagged for dependency shadowing") + t.Error("chart should have been flagged for dependency shadowing") } } } diff --git a/internal/chart/v3/lint/rules/deprecations_test.go b/internal/chart/v3/lint/rules/deprecations_test.go index 35e541e5c..2b12a88f9 100644 --- a/internal/chart/v3/lint/rules/deprecations_test.go +++ b/internal/chart/v3/lint/rules/deprecations_test.go @@ -36,6 +36,6 @@ func TestValidateNoDeprecations(t *testing.T) { APIVersion: "v1", Kind: "Pod", }, nil); err != nil { - t.Errorf("Expected a v1 Pod to not be deprecated") + t.Error("Expected a v1 Pod to not be deprecated") } } diff --git a/internal/chart/v3/lint/rules/values_test.go b/internal/chart/v3/lint/rules/values_test.go index afc544ebd..54c7e6457 100644 --- a/internal/chart/v3/lint/rules/values_test.go +++ b/internal/chart/v3/lint/rules/values_test.go @@ -57,7 +57,7 @@ func TestValidateValuesYamlNotDirectory(t *testing.T) { err := validateValuesFileExistence(nonExistingValuesFilePath) if err == nil { - t.Errorf("validateValuesFileExistence to return a linter error, got no error") + t.Error("validateValuesFileExistence to return a linter error, got no error") } } diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index c32f79aff..c4c252407 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -61,7 +61,7 @@ func TestLoadDirWithDevNull(t *testing.T) { t.Fatalf("Failed to load testdata: %s", err) } if _, err := l.Load(); err == nil { - t.Errorf("packages with an irregular file (/dev/null) should not load") + t.Error("packages with an irregular file (/dev/null) should not load") } } diff --git a/internal/chart/v3/util/chartfile_test.go b/internal/chart/v3/util/chartfile_test.go index 32975d21f..65cb27923 100644 --- a/internal/chart/v3/util/chartfile_test.go +++ b/internal/chart/v3/util/chartfile_test.go @@ -56,11 +56,11 @@ func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { } if f.Maintainers[0].Name != "The Helm Team" { - t.Errorf("Unexpected maintainer name.") + t.Error("Unexpected maintainer name.") } if f.Maintainers[1].Email != "nobody@example.com" { - t.Errorf("Unexpected maintainer email.") + t.Error("Unexpected maintainer email.") } if len(f.Sources) != 1 { @@ -111,7 +111,7 @@ func TestIsChartDir(t *testing.T) { } validChartDir, err = IsChartDir("testdata") if validChartDir || err == nil { - t.Errorf("expected error but did not get any") + t.Error("expected error but did not get any") return } } diff --git a/internal/chart/v3/util/create_test.go b/internal/chart/v3/util/create_test.go index b3b58cc5a..abdd52a82 100644 --- a/internal/chart/v3/util/create_test.go +++ b/internal/chart/v3/util/create_test.go @@ -140,7 +140,7 @@ func TestCreate_Overwrite(t *testing.T) { } if errlog.Len() == 0 { - t.Errorf("Expected warnings about overwriting files.") + t.Error("Expected warnings about overwriting files.") } } diff --git a/internal/plugin/installer/http_installer_test.go b/internal/plugin/installer/http_installer_test.go index 85a84ee31..006b7a7b3 100644 --- a/internal/plugin/installer/http_installer_test.go +++ b/internal/plugin/installer/http_installer_test.go @@ -53,16 +53,16 @@ var fakePluginB64 = "H4sIAAAAAAAAA+3SQUvDMBgG4Jz7K0LwapdvSxrwJig6mCKC5xHabBaXdDS func TestStripName(t *testing.T) { if stripPluginName("fake-plugin-0.0.1.tar.gz") != "fake-plugin" { - t.Errorf("name does not match expected value") + t.Error("name does not match expected value") } if stripPluginName("fake-plugin-0.0.1.tgz") != "fake-plugin" { - t.Errorf("name does not match expected value") + t.Error("name does not match expected value") } if stripPluginName("fake-plugin.tgz") != "fake-plugin" { - t.Errorf("name does not match expected value") + t.Error("name does not match expected value") } if stripPluginName("fake-plugin.tar.gz") != "fake-plugin" { - t.Errorf("name does not match expected value") + t.Error("name does not match expected value") } } @@ -345,7 +345,7 @@ func TestMediaTypeToExtension(t *testing.T) { t.Errorf("Media type %q failed test", mt) } if shouldPass && ext == "" { - t.Errorf("Expected an extension but got empty string") + t.Error("Expected an extension but got empty string") } if !shouldPass && len(ext) != 0 { t.Error("Expected extension to be empty for unrecognized type") diff --git a/internal/plugin/installer/installer.go b/internal/plugin/installer/installer.go index f0870dcc5..1ab14e813 100644 --- a/internal/plugin/installer/installer.go +++ b/internal/plugin/installer/installer.go @@ -99,7 +99,7 @@ func InstallWithOptions(i Installer, opts Options) (*VerificationResult, error) // Check if provenance data exists if len(provData) == 0 { // No .prov file found - emit warning but continue installation - fmt.Fprintf(os.Stderr, "WARNING: No provenance file found for plugin. Plugin is not signed and cannot be verified.\n") + fmt.Fprint(os.Stderr, "WARNING: No provenance file found for plugin. Plugin is not signed and cannot be verified.\n") } else { // Provenance data exists - verify the plugin verification, err := plugin.VerifyPlugin(archiveData, provData, filename, opts.Keyring) diff --git a/internal/plugin/installer/installer_test.go b/internal/plugin/installer/installer_test.go index dcd76fe9c..50e8e1ee2 100644 --- a/internal/plugin/installer/installer_test.go +++ b/internal/plugin/installer/installer_test.go @@ -23,18 +23,18 @@ func TestIsRemoteHTTPArchive(t *testing.T) { source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" if isRemoteHTTPArchive("/not/a/URL") { - t.Errorf("Expected non-URL to return false") + t.Error("Expected non-URL to return false") } // URLs with valid archive extensions are considered valid archives // even if the server is unreachable (optimization to avoid unnecessary HTTP requests) if !isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.tgz") { - t.Errorf("URL with .tgz extension should be considered a valid archive") + t.Error("URL with .tgz extension should be considered a valid archive") } // Test with invalid extension and unreachable server if isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.notanarchive") { - t.Errorf("Bad URL without valid extension should not succeed") + t.Error("Bad URL without valid extension should not succeed") } if !isRemoteHTTPArchive(source) { diff --git a/internal/plugin/installer/oci_installer_test.go b/internal/plugin/installer/oci_installer_test.go index 4576f1c71..1f25f4e76 100644 --- a/internal/plugin/installer/oci_installer_test.go +++ b/internal/plugin/installer/oci_installer_test.go @@ -239,7 +239,7 @@ func TestNewOCIInstaller(t *testing.T) { if tt.expectError { if err == nil { - t.Errorf("expected error but got none") + t.Error("expected error but got none") } return } @@ -416,7 +416,7 @@ func TestOCIInstaller_Install_WithGetterOptions(t *testing.T) { err = Install(installer) if tc.wantErr { if err == nil { - t.Errorf("Expected installation to fail, but it succeeded") + t.Error("Expected installation to fail, but it succeeded") } } else { if err != nil { @@ -526,13 +526,13 @@ func TestOCIInstaller_Install_ComponentExtraction(t *testing.T) { // Verify plugin.yaml exists pluginYAMLPath := filepath.Join(tempDir, "plugin.yaml") if _, err := os.Stat(pluginYAMLPath); os.IsNotExist(err) { - t.Errorf("plugin.yaml not found after extraction") + t.Error("plugin.yaml not found after extraction") } // Verify bin directory exists binPath := filepath.Join(tempDir, "bin") if _, err := os.Stat(binPath); os.IsNotExist(err) { - t.Errorf("bin directory not found after extraction") + t.Error("bin directory not found after extraction") } // Verify executable exists and has correct permissions @@ -540,12 +540,12 @@ func TestOCIInstaller_Install_ComponentExtraction(t *testing.T) { if info, err := os.Stat(execPath); err != nil { t.Errorf("executable not found: %v", err) } else if info.Mode()&0111 == 0 { - t.Errorf("file is not executable") + t.Error("file is not executable") } // Verify this would be recognized as a plugin if !isPlugin(tempDir) { - t.Errorf("extracted directory is not a valid plugin") + t.Error("extracted directory is not a valid plugin") } } @@ -798,7 +798,7 @@ func TestOCIInstaller_Install_ValidationErrors(t *testing.T) { t.Error("expected valid gzip data") } if !strings.Contains(tt.errorMsg, "is not a gzip compressed archive") { - t.Errorf("expected error message to contain 'is not a gzip compressed archive'") + t.Error("expected error message to contain 'is not a gzip compressed archive'") } } }) diff --git a/internal/plugin/installer/verification_test.go b/internal/plugin/installer/verification_test.go index 4227c8058..390624827 100644 --- a/internal/plugin/installer/verification_test.go +++ b/internal/plugin/installer/verification_test.go @@ -118,7 +118,7 @@ func TestInstallWithOptions_VerifyWithValidProvenance(t *testing.T) { // Plugin should not be installed due to verification failure if _, err := os.Stat(installer.Path()); !os.IsNotExist(err) { - t.Errorf("Plugin should not be installed when verification fails") + t.Error("Plugin should not be installed when verification fails") } } @@ -150,7 +150,7 @@ func TestInstallWithOptions_VerifyWithInvalidProvenance(t *testing.T) { // Should fail with verification error if err == nil { - t.Fatalf("Expected installation with invalid .prov file to fail") + t.Fatal("Expected installation with invalid .prov file to fail") } if result != nil { t.Errorf("Expected nil verification result when verification fails, got: %+v", result) @@ -164,7 +164,7 @@ func TestInstallWithOptions_VerifyWithInvalidProvenance(t *testing.T) { // Plugin should not be installed if _, err := os.Stat(installer.Path()); !os.IsNotExist(err) { - t.Errorf("Plugin should not be installed when verification fails") + t.Error("Plugin should not be installed when verification fails") } } diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index 5271e34fa..62e8adfea 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -377,7 +377,7 @@ func TestGetVersionSet(t *testing.T) { } if !vs.Has("v1") { - t.Errorf("Expected supported versions to at least include v1.") + t.Error("Expected supported versions to at least include v1.") } if vs.Has("nosuchversion/v1") { t.Error("Non-existent version is reported found.") diff --git a/pkg/action/hooks_test.go b/pkg/action/hooks_test.go index 4b9b5becb..b017b1f11 100644 --- a/pkg/action/hooks_test.go +++ b/pkg/action/hooks_test.go @@ -403,11 +403,11 @@ data: } if err != nil && !tc.expectError { - t.Fatalf("Got an unexpected error.") + t.Fatal("Got an unexpected error.") } if err == nil && tc.expectError { - t.Fatalf("Expected and error but did not get it.") + t.Fatal("Expected and error but did not get it.") } }) } diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index e4d2b7376..05ca9a75e 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -1270,7 +1270,7 @@ func TestInstallCRDs_CheckNilErrors(t *testing.T) { err := instAction.installCRDs(tt.input) if err == nil { - t.Errorf("got nil expected err") + t.Error("got nil expected err") } }) } diff --git a/pkg/action/lint_test.go b/pkg/action/lint_test.go index 6ee1e07fa..5b5c2c50a 100644 --- a/pkg/action/lint_test.go +++ b/pkg/action/lint_test.go @@ -93,7 +93,7 @@ func TestLintChart(t *testing.T) { case err != nil && !tt.err: t.Errorf("%s", err) case err == nil && tt.err: - t.Errorf("Expected a chart parsing error") + t.Error("Expected a chart parsing error") } }) } diff --git a/pkg/chart/common/capabilities_test.go b/pkg/chart/common/capabilities_test.go index b96d7d29b..c9f026cbb 100644 --- a/pkg/chart/common/capabilities_test.go +++ b/pkg/chart/common/capabilities_test.go @@ -68,7 +68,7 @@ func TestDefaultCapabilities(t *testing.T) { func TestParseKubeVersion(t *testing.T) { kv, err := ParseKubeVersion("v1.16.0") if err != nil { - t.Errorf("Expected v1.16.0 to parse successfully") + t.Error("Expected v1.16.0 to parse successfully") } if kv.Version != "v1.16.0" { t.Errorf("Expected parsed KubeVersion.Version to be v1.16.0, got %q", kv.String()) diff --git a/pkg/chart/common/util/values_test.go b/pkg/chart/common/util/values_test.go index 1dea04132..23b8a3de2 100644 --- a/pkg/chart/common/util/values_test.go +++ b/pkg/chart/common/util/values_test.go @@ -85,7 +85,7 @@ func TestToRenderValues(t *testing.T) { t.Error("Expected upgrade to be false.") } if !relmap["IsInstall"].(bool) { - t.Errorf("Expected install to be true.") + t.Error("Expected install to be true.") } if !res["Capabilities"].(*common.Capabilities).APIVersions.Has("v1") { t.Error("Expected Capabilities to have v1 as an API") diff --git a/pkg/chart/common/values_test.go b/pkg/chart/common/values_test.go index 940d0d451..9743869ec 100644 --- a/pkg/chart/common/values_test.go +++ b/pkg/chart/common/values_test.go @@ -113,7 +113,7 @@ chapter: } if _, err := d.Table("title"); err == nil { - t.Fatalf("Title is not a table.") + t.Fatal("Title is not a table.") } if _, err := d.Table("chapter"); err != nil { @@ -131,7 +131,7 @@ chapter: } if _, err := d.Table("chapter.OneHundredThirtySix"); err == nil { - t.Errorf("I think you mean 'Epilogue'") + t.Error("I think you mean 'Epilogue'") } } @@ -150,13 +150,13 @@ func matchValues(t *testing.T, data map[string]any) { if o, err := ttpl("{{.mariner.shot}}", data); err != nil { t.Errorf(".mariner.shot: %s", err) } else if o != "ALBATROSS" { - t.Errorf("Expected that mariner shot ALBATROSS") + t.Error("Expected that mariner shot ALBATROSS") } if o, err := ttpl("{{.water.water.where}}", data); err != nil { t.Errorf(".water.water.where: %s", err) } else if o != "everywhere" { - t.Errorf("Expected water water everywhere") + t.Error("Expected water water everywhere") } } @@ -199,7 +199,7 @@ chapter: } if v, err := d.PathValue("title"); err == nil { if v != "Moby Dick" { - t.Errorf("Failed to return values for root key title") + t.Error("Failed to return values for root key title") } } } diff --git a/pkg/chart/v2/lint/lint_test.go b/pkg/chart/v2/lint/lint_test.go index b6a5af65e..4256281e0 100644 --- a/pkg/chart/v2/lint/lint_test.go +++ b/pkg/chart/v2/lint/lint_test.go @@ -100,7 +100,7 @@ func TestInvalidYaml(t *testing.T) { t.Fatalf("All didn't fail with expected errors, got %#v", m) } if !strings.Contains(m[0].Err.Error(), "deliberateSyntaxError") { - t.Errorf("All didn't have the error for deliberateSyntaxError") + t.Error("All didn't have the error for deliberateSyntaxError") } } @@ -111,7 +111,7 @@ func TestInvalidChartYaml(t *testing.T) { t.Fatalf("All didn't fail with expected errors, got %#v", m) } if !strings.Contains(m[0].Err.Error(), "failed to strictly parse chart metadata file") { - t.Errorf("All didn't have the error for duplicate YAML keys") + t.Error("All didn't have the error for duplicate YAML keys") } } @@ -241,7 +241,7 @@ func TestMalformedTemplate(t *testing.T) { t.Fatalf("All didn't fail with expected errors, got %#v", m) } if !strings.Contains(m[0].Err.Error(), "invalid character '{'") { - t.Errorf("All didn't have the error for invalid character '{'") + t.Error("All didn't have the error for invalid character '{'") } } } diff --git a/pkg/chart/v2/lint/rules/chartfile_test.go b/pkg/chart/v2/lint/rules/chartfile_test.go index 692358426..c9e202770 100644 --- a/pkg/chart/v2/lint/rules/chartfile_test.go +++ b/pkg/chart/v2/lint/rules/chartfile_test.go @@ -50,26 +50,26 @@ func TestValidateChartYamlNotDirectory(t *testing.T) { err := validateChartYamlNotDirectory(nonExistingChartFilePath) if err == nil { - t.Errorf("validateChartYamlNotDirectory to return a linter error, got no error") + t.Error("validateChartYamlNotDirectory to return a linter error, got no error") } } func TestValidateChartYamlFormat(t *testing.T) { err := validateChartYamlFormat(errors.New("Read error")) if err == nil { - t.Errorf("validateChartYamlFormat to return a linter error, got no error") + t.Error("validateChartYamlFormat to return a linter error, got no error") } err = validateChartYamlFormat(nil) if err != nil { - t.Errorf("validateChartYamlFormat to return no error, got a linter error") + t.Error("validateChartYamlFormat to return no error, got a linter error") } } func TestValidateChartName(t *testing.T) { err := validateChartName(badChart) if err == nil { - t.Errorf("validateChartName to return a linter error, got no error") + t.Error("validateChartName to return a linter error, got no error") } err = validateChartName(badChartName) @@ -176,7 +176,7 @@ func TestValidateChartMaintainer(t *testing.T) { badChart.Maintainers = []*chart.Maintainer{nil} err := validateChartMaintainer(badChart) if err == nil { - t.Errorf("validateChartMaintainer did not return error for nil maintainer as expected") + t.Error("validateChartMaintainer did not return error for nil maintainer as expected") } if err.Error() != "a maintainer entry is empty" { t.Errorf("validateChartMaintainer returned unexpected error for nil maintainer: %s", err.Error()) @@ -212,7 +212,7 @@ func TestValidateChartIconPresence(t *testing.T) { err := validateChartIconPresence(testChart) if err == nil { - t.Errorf("validateChartIconPresence to return a linter error, got no error") + t.Error("validateChartIconPresence to return a linter error, got no error") } else if !strings.Contains(err.Error(), "icon is recommended") { t.Errorf("expected %q, got %q", "icon is recommended", err.Error()) } diff --git a/pkg/chart/v2/lint/rules/dependencies_test.go b/pkg/chart/v2/lint/rules/dependencies_test.go index 08a6646cd..c50fc4b2d 100644 --- a/pkg/chart/v2/lint/rules/dependencies_test.go +++ b/pkg/chart/v2/lint/rules/dependencies_test.go @@ -72,7 +72,7 @@ func TestValidateDependencyInMetadata(t *testing.T) { c := chartWithBadDependencies() if err := validateDependencyInMetadata(&c); err == nil { - t.Errorf("chart should have been flagged for missing deps in chart metadata") + t.Error("chart should have been flagged for missing deps in chart metadata") } } @@ -132,7 +132,7 @@ func TestValidateDependenciesUnique(t *testing.T) { for _, tt := range tests { if err := validateDependenciesUnique(&tt.chart); err == nil { - t.Errorf("chart should have been flagged for dependency shadowing") + t.Error("chart should have been flagged for dependency shadowing") } } } diff --git a/pkg/chart/v2/lint/rules/deprecations_test.go b/pkg/chart/v2/lint/rules/deprecations_test.go index e153f67e6..86643f1c9 100644 --- a/pkg/chart/v2/lint/rules/deprecations_test.go +++ b/pkg/chart/v2/lint/rules/deprecations_test.go @@ -36,6 +36,6 @@ func TestValidateNoDeprecations(t *testing.T) { APIVersion: "v1", Kind: "Pod", }, nil); err != nil { - t.Errorf("Expected a v1 Pod to not be deprecated") + t.Error("Expected a v1 Pod to not be deprecated") } } diff --git a/pkg/chart/v2/lint/rules/values_test.go b/pkg/chart/v2/lint/rules/values_test.go index afc544ebd..54c7e6457 100644 --- a/pkg/chart/v2/lint/rules/values_test.go +++ b/pkg/chart/v2/lint/rules/values_test.go @@ -57,7 +57,7 @@ func TestValidateValuesYamlNotDirectory(t *testing.T) { err := validateValuesFileExistence(nonExistingValuesFilePath) if err == nil { - t.Errorf("validateValuesFileExistence to return a linter error, got no error") + t.Error("validateValuesFileExistence to return a linter error, got no error") } } diff --git a/pkg/chart/v2/loader/load.go b/pkg/chart/v2/loader/load.go index e3c5ce325..28115d062 100644 --- a/pkg/chart/v2/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -120,7 +120,7 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { c.Metadata = new(chart.Metadata) } if c.Metadata.APIVersion != chart.APIVersionV1 { - log.Printf("Warning: Dependencies are handled in Chart.yaml since apiVersion \"v2\". We recommend migrating dependencies to Chart.yaml.") + log.Print("Warning: Dependencies are handled in Chart.yaml since apiVersion \"v2\". We recommend migrating dependencies to Chart.yaml.") } if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil { return c, fmt.Errorf("cannot load requirements.yaml: %w", err) @@ -138,7 +138,7 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { c.Metadata = new(chart.Metadata) } if c.Metadata.APIVersion != chart.APIVersionV1 { - log.Printf("Warning: Dependency locking is handled in Chart.lock since apiVersion \"v2\". We recommend migrating to Chart.lock.") + log.Print("Warning: Dependency locking is handled in Chart.lock since apiVersion \"v2\". We recommend migrating to Chart.lock.") } if c.Metadata.APIVersion == chart.APIVersionV1 { c.Files = append(c.Files, &common.File{Name: f.Name, ModTime: f.ModTime, Data: f.Data}) diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index dad988605..aed071b2f 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -61,7 +61,7 @@ func TestLoadDirWithDevNull(t *testing.T) { t.Fatalf("Failed to load testdata: %s", err) } if _, err := l.Load(); err == nil { - t.Errorf("packages with an irregular file (/dev/null) should not load") + t.Error("packages with an irregular file (/dev/null) should not load") } } diff --git a/pkg/chart/v2/util/chartfile_test.go b/pkg/chart/v2/util/chartfile_test.go index ebadc3414..068977eef 100644 --- a/pkg/chart/v2/util/chartfile_test.go +++ b/pkg/chart/v2/util/chartfile_test.go @@ -60,11 +60,11 @@ func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { } if f.Maintainers[0].Name != "The Helm Team" { - t.Errorf("Unexpected maintainer name.") + t.Error("Unexpected maintainer name.") } if f.Maintainers[1].Email != "nobody@example.com" { - t.Errorf("Unexpected maintainer email.") + t.Error("Unexpected maintainer email.") } if len(f.Sources) != 1 { @@ -115,7 +115,7 @@ func TestIsChartDir(t *testing.T) { } validChartDir, err = IsChartDir("testdata") if validChartDir || err == nil { - t.Errorf("expected error but did not get any") + t.Error("expected error but did not get any") return } } diff --git a/pkg/chart/v2/util/create_test.go b/pkg/chart/v2/util/create_test.go index 086c4e5c8..967972fc8 100644 --- a/pkg/chart/v2/util/create_test.go +++ b/pkg/chart/v2/util/create_test.go @@ -140,7 +140,7 @@ func TestCreate_Overwrite(t *testing.T) { } if errlog.Len() == 0 { - t.Errorf("Expected warnings about overwriting files.") + t.Error("Expected warnings about overwriting files.") } } diff --git a/pkg/cli/values/options_test.go b/pkg/cli/values/options_test.go index 25046214d..571622d80 100644 --- a/pkg/cli/values/options_test.go +++ b/pkg/cli/values/options_test.go @@ -289,7 +289,7 @@ func TestReadFileOriginal(t *testing.T) { filePath := "%a.txt" _, err := readFile(filePath, p) if err == nil { - t.Errorf("Expected error when has special strings") + t.Error("Expected error when has special strings") } } diff --git a/pkg/cmd/completion.go b/pkg/cmd/completion.go index 6f6dbd25d..c02462d9d 100644 --- a/pkg/cmd/completion.go +++ b/pkg/cmd/completion.go @@ -193,7 +193,7 @@ compdef _helm %[1]s } // Cobra doesn't source zsh completion file, explicitly doing it here - fmt.Fprintf(out, "compdef _helm helm") + fmt.Fprint(out, "compdef _helm helm") return err } diff --git a/pkg/cmd/plugin_install.go b/pkg/cmd/plugin_install.go index f79356849..2ce8ad7b0 100644 --- a/pkg/cmd/plugin_install.go +++ b/pkg/cmd/plugin_install.go @@ -132,7 +132,7 @@ func (o *pluginInstallOptions) run(out io.Writer) error { if localInst, ok := i.(*installer.LocalInstaller); ok && !localInst.SupportsVerification() { // Local directory installations are allowed without verification shouldVerify = false - fmt.Fprintf(out, "Installing plugin from local directory (development mode)\n") + fmt.Fprint(out, "Installing plugin from local directory (development mode)\n") } else if shouldVerify { // For remote installations, check if verification is supported if verifier, ok := i.(installer.Verifier); !ok || !verifier.SupportsVerification() { @@ -140,7 +140,7 @@ func (o *pluginInstallOptions) run(out io.Writer) error { } } else { // User explicitly disabled verification - fmt.Fprintf(out, "WARNING: Skipping plugin signature verification\n") + fmt.Fprint(out, "WARNING: Skipping plugin signature verification\n") } // Set up installation options @@ -151,7 +151,7 @@ func (o *pluginInstallOptions) run(out io.Writer) error { // If verify is requested, show verification output if shouldVerify { - fmt.Fprintf(out, "Verifying plugin signature...\n") + fmt.Fprint(out, "Verifying plugin signature...\n") } // Install the plugin with options diff --git a/pkg/cmd/plugin_package.go b/pkg/cmd/plugin_package.go index 6c7ca3050..d82f4d2cc 100644 --- a/pkg/cmd/plugin_package.go +++ b/pkg/cmd/plugin_package.go @@ -119,7 +119,7 @@ func (o *pluginPackageOptions) run(out io.Writer) error { } } else { // User explicitly disabled signing - fmt.Fprintf(out, "WARNING: Skipping plugin signing. This is not recommended for plugins intended for distribution.\n") + fmt.Fprint(out, "WARNING: Skipping plugin signing. This is not recommended for plugins intended for distribution.\n") } // Now create the tarball (only after signing prerequisites are met) diff --git a/pkg/cmd/repo_add_test.go b/pkg/cmd/repo_add_test.go index df9451d34..8f8e36d75 100644 --- a/pkg/cmd/repo_add_test.go +++ b/pkg/cmd/repo_add_test.go @@ -130,7 +130,7 @@ func TestRepoAdd(t *testing.T) { } if err := o.run(io.Discard); err != nil { - t.Errorf("Duplicate repository name was added") + t.Error("Duplicate repository name was added") } } @@ -162,7 +162,7 @@ func TestRepoAddCheckLegalName(t *testing.T) { t.Fatalf("Actual error %s, not equal to expected error %s", err, wantErrorMsg) } } else { - t.Fatalf("expect reported an error.") + t.Fatal("expect reported an error.") } } diff --git a/pkg/cmd/rollback.go b/pkg/cmd/rollback.go index 2cabbb8ef..01d8b1866 100644 --- a/pkg/cmd/rollback.go +++ b/pkg/cmd/rollback.go @@ -76,7 +76,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return err } - fmt.Fprintf(out, "Rollback was a success! Happy Helming!\n") + fmt.Fprint(out, "Rollback was a success! Happy Helming!\n") return nil }, } diff --git a/pkg/cmd/search_hub_test.go b/pkg/cmd/search_hub_test.go index 8e056f771..62881cbdd 100644 --- a/pkg/cmd/search_hub_test.go +++ b/pkg/cmd/search_hub_test.go @@ -168,7 +168,7 @@ func TestSearchHubCmd_FailOnNoResponseTests(t *testing.T) { _, out, err := executeActionCommandC(storage, tt.cmd) if tt.wantErr { if err == nil { - t.Errorf("expected error due to no record in response, got nil") + t.Error("expected error due to no record in response, got nil") } } else { if err != nil { diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 09edc3337..c674a11ec 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -1466,7 +1466,7 @@ func TestTraceableError_SimpleForm(t *testing.T) { for _, errString := range testStrings { trace, done := parseTemplateSimpleErrorString(errString) if !done { - t.Errorf("Expected parse to pass but did not") + t.Error("Expected parse to pass but did not") } if trace.message != "error calling include" { t.Errorf("Expected %q, got %q", errString, trace.message) @@ -1483,7 +1483,7 @@ func TestTraceableError_ExecutingForm(t *testing.T) { expectedLocation := errTuple[1] trace, done := parseTemplateExecutingAtErrorType(errString) if !done { - t.Errorf("Expected parse to pass but did not") + t.Error("Expected parse to pass but did not") } if trace.location != expectedLocation { t.Errorf("Expected %q, got %q", expectedLocation, trace.location) @@ -1498,7 +1498,7 @@ func TestTraceableError_NoTemplateForm(t *testing.T) { for _, errString := range testStrings { trace, done := parseTemplateNoTemplateError(errString, errString) if !done { - t.Errorf("Expected parse to pass but did not") + t.Error("Expected parse to pass but did not") } if trace.message != errString { t.Errorf("Expected %q, got %q", errString, trace.message) diff --git a/pkg/kube/wait_test.go b/pkg/kube/wait_test.go index b088dc5a3..73270b5a3 100644 --- a/pkg/kube/wait_test.go +++ b/pkg/kube/wait_test.go @@ -299,7 +299,7 @@ func TestLegacyWaiter_waitForPodSuccess(t *testing.T) { done, err := lw.waitForPodSuccess(tt.obj, "foo") if tt.wantErr { if err == nil { - t.Errorf("expected error, got none") + t.Error("expected error, got none") } else if !strings.Contains(err.Error(), tt.errMessage) { t.Errorf("expected error to contain %q, got %q", tt.errMessage, err.Error()) } @@ -391,7 +391,7 @@ func TestLegacyWaiter_waitForJob(t *testing.T) { done, err := lw.waitForJob(tt.obj, "test-job") if tt.wantErr { if err == nil { - t.Errorf("expected error, got none") + t.Error("expected error, got none") } else if !strings.Contains(err.Error(), tt.errMessage) { t.Errorf("expected error to contain %q, got %q", tt.errMessage, err.Error()) } diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go index c4dda887b..8784e6c12 100644 --- a/pkg/provenance/sign_test.go +++ b/pkg/provenance/sign_test.go @@ -134,7 +134,7 @@ func TestParseMessageBlock(t *testing.T) { } if hash, ok := sc.Files["hashtest-1.2.3.tgz"]; !ok { - t.Errorf("hashtest file not found in Files") + t.Error("hashtest file not found in Files") } else if hash != "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888" { t.Errorf("Unexpected hash: %q", hash) } diff --git a/pkg/registry/client_test.go b/pkg/registry/client_test.go index 74711f6c3..702dfff69 100644 --- a/pkg/registry/client_test.go +++ b/pkg/registry/client_test.go @@ -91,7 +91,7 @@ func TestLogin_ResetsForceAttemptOAuth2_OnSuccess(t *testing.T) { } if c.authorizer.ForceAttemptOAuth2 { - t.Errorf("ForceAttemptOAuth2 should be false after successful Login") + t.Error("ForceAttemptOAuth2 should be false after successful Login") } } @@ -117,7 +117,7 @@ func TestLogin_ResetsForceAttemptOAuth2_OnFailure(t *testing.T) { _ = c.Login(host, LoginOptPlainText(true), LoginOptBasicAuth("u", "p")) if c.authorizer.ForceAttemptOAuth2 { - t.Errorf("ForceAttemptOAuth2 should be false after failed Login") + t.Error("ForceAttemptOAuth2 should be false after failed Login") } } diff --git a/pkg/registry/plugin_test.go b/pkg/registry/plugin_test.go index f8525829c..61995ece9 100644 --- a/pkg/registry/plugin_test.go +++ b/pkg/registry/plugin_test.go @@ -75,7 +75,7 @@ func TestGetPluginName(t *testing.T) { if tt.expectErr { if err == nil { - t.Errorf("expected error but got none") + t.Error("expected error but got none") } return } diff --git a/pkg/repo/v1/chartrepo_test.go b/pkg/repo/v1/chartrepo_test.go index 7cffc04b6..a0a8dcfc4 100644 --- a/pkg/repo/v1/chartrepo_test.go +++ b/pkg/repo/v1/chartrepo_test.go @@ -85,7 +85,7 @@ func TestIndexCustomSchemeDownload(t *testing.T) { } if len(myCustomGetter.repoUrls) != 1 { - t.Fatalf("Custom Getter.Get should be called once") + t.Fatal("Custom Getter.Get should be called once") } expectedRepoIndexURL := repoURL + "/index.yaml" @@ -240,7 +240,7 @@ func TestErrorFindChartInRepoURL(t *testing.T) { }) if _, err := FindChartInRepoURL("http://someserver/something", "nginx", g); err == nil { - t.Errorf("Expected error for bad chart URL, but did not get any errors") + t.Error("Expected error for bad chart URL, but did not get any errors") } else if !strings.Contains(err.Error(), `looks like "http://someserver/something" is not a valid chart repository or cannot be reached`) { t.Errorf("Expected error for bad chart URL, but got a different error (%v)", err) } @@ -252,22 +252,22 @@ func TestErrorFindChartInRepoURL(t *testing.T) { defer srv.Close() if _, err = FindChartInRepoURL(srv.URL, "nginx1", g); err == nil { - t.Errorf("Expected error for chart not found, but did not get any errors") + t.Error("Expected error for chart not found, but did not get any errors") } else if err.Error() != `chart "nginx1" not found in `+srv.URL+` repository` { t.Errorf("Expected error for chart not found, but got a different error (%v)", err) } if !errors.Is(err, ChartNotFoundError{}) { - t.Errorf("error is not of correct error type structure") + t.Error("error is not of correct error type structure") } if _, err = FindChartInRepoURL(srv.URL, "nginx1", g, WithChartVersion("0.1.0")); err == nil { - t.Errorf("Expected error for chart not found, but did not get any errors") + t.Error("Expected error for chart not found, but did not get any errors") } else if err.Error() != `chart "nginx1" version "0.1.0" not found in `+srv.URL+` repository` { t.Errorf("Expected error for chart not found, but got a different error (%v)", err) } if _, err = FindChartInRepoURL(srv.URL, "chartWithNoURL", g); err == nil { - t.Errorf("Expected error for no chart URLs available, but did not get any errors") + t.Error("Expected error for no chart URLs available, but did not get any errors") } else if err.Error() != `chart "chartWithNoURL" has no downloadable URLs` { t.Errorf("Expected error for chart not found, but got a different error (%v)", err) } diff --git a/pkg/repo/v1/index_test.go b/pkg/repo/v1/index_test.go index 6c6bb7835..550c8e82c 100644 --- a/pkg/repo/v1/index_test.go +++ b/pkg/repo/v1/index_test.go @@ -130,12 +130,12 @@ func TestIndexFile(t *testing.T) { cv, err = i.Get("setter", "0.1.9+alpha") if err != nil || cv.Version != "0.1.9+alpha" { - t.Errorf("Expected version: 0.1.9+alpha") + t.Error("Expected version: 0.1.9+alpha") } cv, err = i.Get("setter", "0.1.8") if err != nil || cv.Version != "0.1.8" { - t.Errorf("Expected version: 0.1.8") + t.Error("Expected version: 0.1.8") } } @@ -174,7 +174,7 @@ func TestLoadIndex(t *testing.T) { // TestLoadIndex_Duplicates is a regression to make sure that we don't non-deterministically allow duplicate packages. func TestLoadIndex_Duplicates(t *testing.T) { if _, err := loadIndex([]byte(indexWithDuplicates), "indexWithDuplicates"); err == nil { - t.Errorf("Expected an error when duplicate entries are present") + t.Error("Expected an error when duplicate entries are present") } } @@ -186,7 +186,7 @@ func TestLoadIndex_EmptyEntry(t *testing.T) { func TestLoadIndex_Empty(t *testing.T) { if _, err := loadIndex([]byte(""), "indexWithEmpty"); err == nil { - t.Errorf("Expected an error when index.yaml is empty.") + t.Error("Expected an error when index.yaml is empty.") } } @@ -360,7 +360,7 @@ func verifyLocalIndex(t *testing.T, i *IndexFile) { alpine, ok := i.Entries["alpine"] if !ok { - t.Fatalf("'alpine' section not found.") + t.Fatal("'alpine' section not found.") } if l := len(alpine); l != 1 { @@ -595,7 +595,7 @@ func TestAddFileIndexEntriesNil(t *testing.T) { {&chart.Metadata{APIVersion: "v2", Name: " ", Version: "8033-5.apinie+s.r"}, "setter-0.1.9+beta.tgz", "http://example.com/charts", "sha256:1234567890abc"}, } { if err := i.MustAdd(x.md, x.filename, x.baseURL, x.digest); err == nil { - t.Errorf("expected err to be non-nil when entries not initialized") + t.Error("expected err to be non-nil when entries not initialized") } } } diff --git a/pkg/repo/v1/repo_test.go b/pkg/repo/v1/repo_test.go index 93745be1b..9b5c54309 100644 --- a/pkg/repo/v1/repo_test.go +++ b/pkg/repo/v1/repo_test.go @@ -219,9 +219,9 @@ func TestWriteFile(t *testing.T) { func TestRepoNotExists(t *testing.T) { if _, err := LoadFile("/this/path/does/not/exist.yaml"); err == nil { - t.Errorf("expected err to be non-nil when path does not exist") + t.Error("expected err to be non-nil when path does not exist") } else if !strings.Contains(err.Error(), "couldn't load repositories file") { - t.Errorf("expected prompt `couldn't load repositories file`") + t.Error("expected prompt `couldn't load repositories file`") } } diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go index 329b82b2f..c6401b425 100644 --- a/pkg/storage/driver/memory_test.go +++ b/pkg/storage/driver/memory_test.go @@ -280,7 +280,7 @@ func TestMemoryDelete(t *testing.T) { } _, err = ts.Get(tt.key) if err == nil { - t.Errorf("Expected an error when asking for a deleted key") + t.Error("Expected an error when asking for a deleted key") } } diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index 0ae5321ba..9b4203224 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -589,7 +589,7 @@ func TestStorageGetsLoggerFromDriver(t *testing.T) { s := Init(d) _, _ = s.Get("doesnt-matter", 123) if !l.Called { - t.Fatalf("Expected storage to use driver's logger, but it did not") + t.Fatal("Expected storage to use driver's logger, but it did not") } } From b075f7a35d25ec0a4414b011142744f8f1821b47 Mon Sep 17 00:00:00 2001 From: Ilya Kiselev Date: Mon, 23 Mar 2026 18:20:54 +0300 Subject: [PATCH 234/571] feat(engine): add mustToToml template function Add `mustToToml` that panics on marshal error, consistent with `mustToYaml` and `mustToJson`. This makes it possible for chart authors to get a hard failure when TOML serialization fails, rather than having to inspect the output manually. `toToml` behavior is unchanged in this commit. Closes #31430 Signed-off-by: Ilya Kiselev --- pkg/engine/funcs.go | 16 ++++++++++++++++ pkg/engine/funcs_test.go | 7 +++++++ 2 files changed, 23 insertions(+) diff --git a/pkg/engine/funcs.go b/pkg/engine/funcs.go index ba842a51a..97db47dfd 100644 --- a/pkg/engine/funcs.go +++ b/pkg/engine/funcs.go @@ -50,6 +50,7 @@ func funcMap() template.FuncMap { // Add some extra functionality extra := template.FuncMap{ "toToml": toTOML, + "mustToToml": mustToTOML, "fromToml": fromTOML, "toYaml": toYAML, "mustToYaml": mustToYAML, @@ -162,6 +163,21 @@ func toTOML(v any) string { return b.String() } +// mustToTOML takes an interface, marshals it to toml, and returns a string. +// It will panic if there is an error. +// +// This is designed to be called from a template when you need to ensure that the +// output TOML is valid. +func mustToTOML(v any) string { + b := bytes.NewBuffer(nil) + e := toml.NewEncoder(b) + err := e.Encode(v) + if err != nil { + panic(err) + } + return b.String() +} + // fromTOML converts a TOML document into a map[string]interface{}. // // This is not a general-purpose TOML parser, and will not parse all valid diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go index 48202454e..be9d0153f 100644 --- a/pkg/engine/funcs_test.go +++ b/pkg/engine/funcs_test.go @@ -159,6 +159,13 @@ keyInElement1 = "valueInElement1"`, tpl: `{{ toJson . }}`, expect: "", // should return empty string and swallow error vars: loopMap, + }, { + tpl: `{{ mustToToml . }}`, + vars: map[int]string{1: "one"}, // non-string key is invalid in TOML + }, { + tpl: `{{ mustToToml . }}`, + expect: "foo = \"bar\"\n", // should succeed and return TOML string + vars: map[string]string{"foo": "bar"}, }, } From c1a5a6e260bd070bce9a8299795400340e10c468 Mon Sep 17 00:00:00 2001 From: Ilya Kiselev Date: Mon, 23 Mar 2026 18:38:57 +0300 Subject: [PATCH 235/571] docs(engine): fix misleading toTOML doc comment The toTOML doc comment said "returns empty string on marshal error" but the implementation actually returns err.Error(). Fix the comment to match the real behavior. Also mention mustToToml as the strict alternative. Signed-off-by: Ilya Kiselev --- pkg/engine/funcs.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/engine/funcs.go b/pkg/engine/funcs.go index 97db47dfd..e03c13b38 100644 --- a/pkg/engine/funcs.go +++ b/pkg/engine/funcs.go @@ -149,10 +149,11 @@ func fromYAMLArray(str string) []any { return a } -// toTOML takes an interface, marshals it to toml, and returns a string. It will -// always return a string, even on marshal error (empty string). +// toTOML takes an interface, marshals it to toml, and returns a string. +// On marshal error it returns the error string. // -// This is designed to be called from a template. +// This is designed to be called from a template. Use mustToToml if you need +// the template to fail hard on marshal errors. func toTOML(v any) string { b := bytes.NewBuffer(nil) e := toml.NewEncoder(b) From 0229da1803a29671b1becc4561c77f85db609aac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:35:24 +0000 Subject: [PATCH 236/571] chore(deps): bump github.com/fatih/color from 1.18.0 to 1.19.0 Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.18.0 to 1.19.0. - [Release notes](https://github.com/fatih/color/releases) - [Commits](https://github.com/fatih/color/compare/v1.18.0...v1.19.0) --- updated-dependencies: - dependency-name: github.com/fatih/color dependency-version: 1.19.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index a34f5cdac..ef98b5f6b 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/distribution/distribution/v3 v3.0.0 github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 - github.com/fatih/color v1.18.0 + github.com/fatih/color v1.19.0 github.com/fluxcd/cli-utils v0.37.2-flux.1 github.com/foxcpp/go-mockdns v1.2.0 github.com/gobwas/glob v0.2.3 @@ -105,7 +105,7 @@ require ( github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.9.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/miekg/dns v1.1.57 // indirect diff --git a/go.sum b/go.sum index 982bae13a..905497f55 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2 github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/extism/go-sdk v1.7.1 h1:lWJos6uY+tRFdlIHR+SJjwFDApY7OypS/2nMhiVQ9Sw= github.com/extism/go-sdk v1.7.1/go.mod h1:IT+Xdg5AZM9hVtpFUA+uZCJMge/hbvshl8bwzLtFyKA= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fluxcd/cli-utils v0.37.2-flux.1 h1:tQ588ghtRN+E+kHq415FddfqA9v4brn/1WWgrP6rQR0= @@ -198,9 +198,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= @@ -423,7 +422,6 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 3a7573a81ed5be2e00dfb74fa8d95c0cbe1c4f0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:35:27 +0000 Subject: [PATCH 237/571] chore(deps): bump github.com/lib/pq from 1.11.2 to 1.12.0 Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.11.2 to 1.12.0. - [Release notes](https://github.com/lib/pq/releases) - [Changelog](https://github.com/lib/pq/blob/master/CHANGELOG.md) - [Commits](https://github.com/lib/pq/compare/v1.11.2...v1.12.0) --- updated-dependencies: - dependency-name: github.com/lib/pq dependency-version: 1.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a34f5cdac..37374acdd 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/gofrs/flock v0.13.0 github.com/gosuri/uitable v0.0.4 github.com/jmoiron/sqlx v1.4.0 - github.com/lib/pq v1.11.2 + github.com/lib/pq v1.12.0 github.com/mattn/go-shellwords v1.0.12 github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 diff --git a/go.sum b/go.sum index 982bae13a..79eef0962 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= -github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo= +github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= From c7a75b16cb8b0859bf32bf74ae98300e5b55361b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:35:31 +0000 Subject: [PATCH 238/571] chore(deps): bump github.com/ProtonMail/go-crypto from 1.3.0 to 1.4.1 Bumps [github.com/ProtonMail/go-crypto](https://github.com/ProtonMail/go-crypto) from 1.3.0 to 1.4.1. - [Release notes](https://github.com/ProtonMail/go-crypto/releases) - [Commits](https://github.com/ProtonMail/go-crypto/compare/v1.3.0...v1.4.1) --- updated-dependencies: - dependency-name: github.com/ProtonMail/go-crypto dependency-version: 1.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a34f5cdac..37a2fbfe3 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/Masterminds/sprig/v3 v3.3.0 github.com/Masterminds/squirrel v1.5.4 github.com/Masterminds/vcs v1.13.3 - github.com/ProtonMail/go-crypto v1.3.0 + github.com/ProtonMail/go-crypto v1.4.1 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/cyphar/filepath-securejoin v0.6.1 github.com/distribution/distribution/v3 v3.0.0 diff --git a/go.sum b/go.sum index 982bae13a..a2ae20395 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8 github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Masterminds/vcs v1.13.3 h1:IIA2aBdXvfbIM+yl/eTnL4hb1XwdpvuQLglAix1gweE= github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8= -github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= -github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= +github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= From 154993723aadf45601d124c6750e8f4ae3b9f2fd Mon Sep 17 00:00:00 2001 From: Sumit Solanki Date: Thu, 26 Mar 2026 20:42:56 +0530 Subject: [PATCH 239/571] refactor(cli): decouple EnvSettings from pkg/kube to avoid import cycles Move the retrying round tripper used by EnvSettings into pkg/cli so pkg/cli no longer imports pkg/kube. This preserves retry behavior while breaking the import edge that triggers cycles for Helm library consumers (such as the kustomize integration described in #31965). Signed-off-by: Sumit Solanki --- pkg/cli/environment.go | 3 +- pkg/cli/roundtripper.go | 82 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 pkg/cli/roundtripper.go diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 83b6bdbba..8faa628fc 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -35,7 +35,6 @@ import ( "helm.sh/helm/v4/internal/version" "helm.sh/helm/v4/pkg/helmpath" - "helm.sh/helm/v4/pkg/kube" ) // defaultMaxHistory sets the maximum number of releases to 0: unlimited @@ -134,7 +133,7 @@ func New() *EnvSettings { config.Burst = env.BurstLimit config.QPS = env.QPS config.Wrap(func(rt http.RoundTripper) http.RoundTripper { - return &kube.RetryingRoundTripper{Wrapped: rt} + return &retryingRoundTripper{wrapped: rt} }) config.UserAgent = version.GetUserAgent() return config diff --git a/pkg/cli/roundtripper.go b/pkg/cli/roundtripper.go new file mode 100644 index 000000000..040ecd48f --- /dev/null +++ b/pkg/cli/roundtripper.go @@ -0,0 +1,82 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cli + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "strings" +) + +// retryingRoundTripper retries selected transient Kubernetes API server failures. +// Keeping this in pkg/cli avoids importing pkg/kube from EnvSettings. +type retryingRoundTripper struct { + wrapped http.RoundTripper +} + +func (rt *retryingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + return rt.roundTrip(req, 1, nil) +} + +func (rt *retryingRoundTripper) roundTrip(req *http.Request, retry int, prevResp *http.Response) (*http.Response, error) { + if retry < 0 { + return prevResp, nil + } + resp, rtErr := rt.wrapped.RoundTrip(req) + if rtErr != nil { + return resp, rtErr + } + if resp.StatusCode < 500 { + return resp, rtErr + } + if resp.Header.Get("content-type") != "application/json" { + return resp, rtErr + } + b, err := io.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return resp, err + } + + var ke kubernetesError + r := bytes.NewReader(b) + err = json.NewDecoder(r).Decode(&ke) + r.Seek(0, io.SeekStart) + resp.Body = io.NopCloser(r) + if err != nil { + return resp, err + } + if ke.Code < 500 { + return resp, nil + } + // Matches messages like "etcdserver: leader changed" + if strings.HasSuffix(ke.Message, "etcdserver: leader changed") { + return rt.roundTrip(req, retry-1, resp) + } + // Matches messages like "rpc error: code = Unknown desc = raft proposal dropped" + if strings.HasSuffix(ke.Message, "raft proposal dropped") { + return rt.roundTrip(req, retry-1, resp) + } + return resp, nil +} + +type kubernetesError struct { + Message string `json:"message"` + Code int `json:"code"` +} From 85bf56ea82fd21452e53cae91b380b0afb3e8b83 Mon Sep 17 00:00:00 2001 From: Debasish Mohanty <139056705+Debasish-87@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:54:38 +0530 Subject: [PATCH 240/571] docs(registry): fix incorrect and improve clarity of comments in client.go - Fixed incorrect comment in ClientOptPlainHTTP - Improved clarity of LoginOptPlainText comment - Enhanced Login function documentation - Improved wording for ClientOptHTTPClient No functional changes. Signed-off-by: Debasish Mohanty <139056705+Debasish-87@users.noreply.github.com> --- pkg/registry/client.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 62e560dba..f2bfd13b4 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -202,13 +202,15 @@ func ClientOptCredentialsFile(credentialsFile string) ClientOption { } } -// ClientOptHTTPClient returns a function that sets the httpClient setting on a client options set +// ClientOptHTTPClient returns a function that sets the HTTP client for the registry client. func ClientOptHTTPClient(httpClient *http.Client) ClientOption { return func(client *Client) { client.httpClient = httpClient } } +// ClientOptPlainHTTP returns a function that enables plain HTTP (non-TLS) +// communication for the registry client. func ClientOptPlainHTTP() ClientOption { return func(c *Client) { c.plainHTTP = true @@ -236,7 +238,7 @@ func warnIfHostHasPath(host string) bool { return false } -// Login logs into a registry +// Login authenticates the client with a remote OCI registry using the provided host and options. func (c *Client) Login(host string, options ...LoginOption) error { for _, option := range options { option(&loginOperation{host, c}) @@ -282,7 +284,8 @@ func LoginOptBasicAuth(username string, password string) LoginOption { } } -// LoginOptPlainText returns a function that allows plaintext (HTTP) login +// LoginOptPlainText returns a function that enables plaintext (HTTP) login +// instead of HTTPS for the registry client. func LoginOptPlainText(isPlainText bool) LoginOption { return func(o *loginOperation) { o.client.plainHTTP = isPlainText From 64f1d0af5b53f0a9292af2ba1efc42a46a57ed00 Mon Sep 17 00:00:00 2001 From: Sumit Solanki Date: Fri, 27 Mar 2026 11:56:33 +0530 Subject: [PATCH 241/571] refactor(cli): decouple EnvSettings from pkg/kube Move the retrying HTTP round-tripper used by EnvSettings into pkg/cli so pkg/cli no longer imports pkg/kube, avoiding import cycles for Helm library consumers while preserving retry behavior for transient API server errors. Add pkg/cli/roundtripper_test.go with parity coverage for the moved logic. Signed-off-by: Sumit Solanki Made-with: Cursor --- pkg/cli/roundtripper_test.go | 161 +++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 pkg/cli/roundtripper_test.go diff --git a/pkg/cli/roundtripper_test.go b/pkg/cli/roundtripper_test.go new file mode 100644 index 000000000..68fc075b0 --- /dev/null +++ b/pkg/cli/roundtripper_test.go @@ -0,0 +1,161 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cli + +import ( + "encoding/json" + "errors" + "io" + "net/http" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +type fakeRoundTripper struct { + resp *http.Response + err error + calls int +} + +func (f *fakeRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) { + f.calls++ + return f.resp, f.err +} + +func newRespWithBody(statusCode int, contentType, body string) *http.Response { + return &http.Response{ + StatusCode: statusCode, + Header: http.Header{"Content-Type": []string{contentType}}, + Body: io.NopCloser(strings.NewReader(body)), + } +} + +func TestRetryingRoundTripper_RoundTrip(t *testing.T) { + marshalErr := func(code int, msg string) string { + b, _ := json.Marshal(kubernetesError{ + Code: code, + Message: msg, + }) + return string(b) + } + + tests := []struct { + name string + resp *http.Response + err error + expectedCalls int + expectedErr string + expectedCode int + }{ + { + name: "no retry, status < 500 returns response", + resp: newRespWithBody(200, "application/json", `{"message":"ok","code":200}`), + err: nil, + expectedCalls: 1, + expectedCode: 200, + }, + { + name: "error from wrapped RoundTripper propagates", + resp: nil, + err: errors.New("wrapped error"), + expectedCalls: 1, + expectedErr: "wrapped error", + }, + { + name: "no retry, content-type not application/json", + resp: newRespWithBody(500, "text/plain", "server error"), + err: nil, + expectedCalls: 1, + expectedCode: 500, + }, + { + name: "error reading body returns error", + resp: &http.Response{ + StatusCode: http.StatusInternalServerError, + Header: http.Header{"Content-Type": []string{"application/json"}}, + Body: &errReader{}, + }, + err: nil, + expectedCalls: 1, + expectedErr: "read error", + }, + { + name: "error decoding JSON returns error", + resp: newRespWithBody(500, "application/json", `invalid-json`), + err: nil, + expectedCalls: 1, + expectedErr: "invalid character", + }, + { + name: "retry on etcdserver leader changed message", + resp: newRespWithBody(500, "application/json", marshalErr(500, "some error etcdserver: leader changed")), + err: nil, + expectedCalls: 2, + expectedCode: 500, + }, + { + name: "retry on raft proposal dropped message", + resp: newRespWithBody(500, "application/json", marshalErr(500, "rpc error: code = Unknown desc = raft proposal dropped")), + err: nil, + expectedCalls: 2, + expectedCode: 500, + }, + { + name: "no retry on other error message", + resp: newRespWithBody(500, "application/json", marshalErr(500, "other server error")), + err: nil, + expectedCalls: 1, + expectedCode: 500, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeRT := &fakeRoundTripper{ + resp: tt.resp, + err: tt.err, + } + rt := retryingRoundTripper{ + wrapped: fakeRT, + } + req, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) + resp, err := rt.RoundTrip(req) + + if tt.expectedErr != "" { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedErr) + return + } + assert.NoError(t, err) + + assert.Equal(t, tt.expectedCode, resp.StatusCode) + assert.Equal(t, tt.expectedCalls, fakeRT.calls) + }) + } +} + +type errReader struct{} + +func (e *errReader) Read(_ []byte) (int, error) { + return 0, errors.New("read error") +} + +func (e *errReader) Close() error { + return nil +} From 7025480397d8b6b7fd8cdb5e083dc37b62dbd3d8 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Fri, 27 Mar 2026 07:45:43 +0100 Subject: [PATCH 242/571] fix: pin codeql-action/upload-sarif to commit SHA in scorecards workflow Pin the remaining unpinned GitHub Action reference to a full commit SHA, matching the pinning convention already used across other workflows in this repository. Aligns with the Kubernetes GitHub Actions security policy. Signed-off-by: Terry Howe --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index d2bf4e56a..a4dc71bcd 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7 with: sarif_file: results.sarif From f257c95c783f5595e36cb5a7dcc862cc1f6266b5 Mon Sep 17 00:00:00 2001 From: abhay1999 Date: Mon, 30 Mar 2026 19:13:32 +0530 Subject: [PATCH 243/571] fix(kube): clarify server-side apply patch errors Signed-off-by: abhay1999 --- pkg/kube/client.go | 2 +- pkg/kube/client_test.go | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 44f31cdbe..c955e8875 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -1241,7 +1241,7 @@ func patchResourceServerSide(target *resource.Info, dryRun bool, forceConflicts return fmt.Errorf("conflict occurred while applying object %s/%s %s: %w", target.Namespace, target.Name, target.Mapping.GroupVersionKind.String(), err) } - return err + return fmt.Errorf("server-side apply failed for object %s/%s %s: %w", target.Namespace, target.Name, target.Mapping.GroupVersionKind.String(), err) } return target.Refresh(obj, true) diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 31894f68e..e98d87520 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -1802,6 +1802,23 @@ func TestPatchResourceServerSide(t *testing.T) { }, ExpectedErrorContains: "the server reported a conflict", }, + "generic server-side apply error": { + Pods: newPodList("whale"), + DryRun: false, + ForceConflicts: false, + FieldValidationDirective: FieldValidationDirectiveStrict, + Callback: func(t *testing.T, _ testCase, _ []RequestResponseAction, _ *http.Request) (*http.Response, error) { + t.Helper() + + return newResponse(http.StatusBadRequest, &metav1.Status{ + Status: metav1.StatusFailure, + Message: `failed to create typed patch object: .spec.template.spec.containers[name="test"].env: duplicate entries for key [name="SERVER_CONTEXT_PATH"]`, + Reason: metav1.StatusReasonBadRequest, + Code: http.StatusBadRequest, + }) + }, + ExpectedErrorContains: "server-side apply failed for object default/whale /v1, Kind=Pod: failed to create typed patch object", + }, } for name, tc := range testCases { From d1e31ca507412d770a602e722060e6d7379f4f1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:15:34 +0000 Subject: [PATCH 244/571] chore(deps): bump actions/setup-go from 6.2.0 to 6.4.0 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.2.0 to 6.4.0. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5...4a3601121dd01d1626a1e23e37211e3254c1c06c) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 6.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/release.yml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index f2c472faa..32c524ed3 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -22,7 +22,7 @@ jobs: - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # pin@6.2.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 142cb3533..8b64a3be2 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -17,7 +17,7 @@ jobs: - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # pin@6.2.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 1bfc81b66..6453ddd40 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -25,7 +25,7 @@ jobs: - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # pin@6.2.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 017687cc1..593003192 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # pin@6.2.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true @@ -91,7 +91,7 @@ jobs: run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # pin@6.2.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true From 9a0674188412d1dcb2e7f018730aaa71781bd03b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:17:45 +0000 Subject: [PATCH 245/571] chore(deps): bump github.com/lib/pq from 1.12.0 to 1.12.1 Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.12.0 to 1.12.1. - [Release notes](https://github.com/lib/pq/releases) - [Changelog](https://github.com/lib/pq/blob/master/CHANGELOG.md) - [Commits](https://github.com/lib/pq/compare/v1.12.0...v1.12.1) --- updated-dependencies: - dependency-name: github.com/lib/pq dependency-version: 1.12.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4b8bc5636..d47b613a3 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/gofrs/flock v0.13.0 github.com/gosuri/uitable v0.0.4 github.com/jmoiron/sqlx v1.4.0 - github.com/lib/pq v1.12.0 + github.com/lib/pq v1.12.1 github.com/mattn/go-shellwords v1.0.12 github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 diff --git a/go.sum b/go.sum index ec5536a73..52c73ea17 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo= -github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lib/pq v1.12.1 h1:x1nbl/338GLqeDJ/FAiILallhAsqubLzEZu/pXtHUow= +github.com/lib/pq v1.12.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= From 45ee55b83f8ad23798c84560ff65686e2ab298af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 02:02:41 +0000 Subject: [PATCH 246/571] chore(deps): bump github/codeql-action from 4.30.7 to 4.35.1 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.30.7 to 4.35.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/e296a935590eb16afc0c0108289f68c87e2a89a5...c10b8064de6f491fea524254123dbe5e09572f13) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.35.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b66bbc29d..7916808e7 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@e296a935590eb16afc0c0108289f68c87e2a89a5 # pinv4.30.7 + uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # pinv4.35.1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -58,7 +58,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@e296a935590eb16afc0c0108289f68c87e2a89a5 # pinv4.30.7 + uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # pinv4.35.1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -72,4 +72,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e296a935590eb16afc0c0108289f68c87e2a89a5 # pinv4.30.7 + uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # pinv4.35.1 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index a4dc71bcd..0a84609ab 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7 + uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: sarif_file: results.sarif From cd7cf76a174e856fd171b391995d9a65f97a79d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 21:33:54 +0000 Subject: [PATCH 247/571] chore(deps): bump github.com/lib/pq from 1.12.1 to 1.12.2 Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.12.1 to 1.12.2. - [Release notes](https://github.com/lib/pq/releases) - [Changelog](https://github.com/lib/pq/blob/master/CHANGELOG.md) - [Commits](https://github.com/lib/pq/compare/v1.12.1...v1.12.2) --- updated-dependencies: - dependency-name: github.com/lib/pq dependency-version: 1.12.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d47b613a3..2ff16014a 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/gofrs/flock v0.13.0 github.com/gosuri/uitable v0.0.4 github.com/jmoiron/sqlx v1.4.0 - github.com/lib/pq v1.12.1 + github.com/lib/pq v1.12.2 github.com/mattn/go-shellwords v1.0.12 github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 diff --git a/go.sum b/go.sum index 52c73ea17..81c537cc6 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.12.1 h1:x1nbl/338GLqeDJ/FAiILallhAsqubLzEZu/pXtHUow= -github.com/lib/pq v1.12.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lib/pq v1.12.2 h1:ajJNv84limnK3aPbDIhLtcjrUbqAw/5XNdkuI6KNe/Q= +github.com/lib/pq v1.12.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= From 8ef2d45934ba1b9ca341818f1157112fcf7cdf1d Mon Sep 17 00:00:00 2001 From: Rhys McNeill Date: Sun, 5 Apr 2026 15:55:56 +0100 Subject: [PATCH 248/571] fix(action): return correct error variable in prepareUpgrade When Deployed() succeeds but releaserToV1Release() fails, prepareUpgrade returned err (nil) instead of cerr (the conversion error), causing a silent nil return that could lead to nil pointer dereferences downstream. Closes #32007 Signed-off-by: Rhys McNeill --- pkg/action/upgrade.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 0f360fe37..103ab4fdb 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -253,7 +253,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str var cerr error currentRelease, cerr = releaserToV1Release(currentReleasei) if cerr != nil { - return nil, nil, false, err + return nil, nil, false, cerr } if err != nil { if errors.Is(err, driver.ErrNoDeployedReleases) && From 9b1ad4cf027452b828affb07318db2e931e734a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:53:50 +0000 Subject: [PATCH 249/571] chore(deps): bump github.com/distribution/distribution/v3 Bumps [github.com/distribution/distribution/v3](https://github.com/distribution/distribution) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/distribution/distribution/releases) - [Commits](https://github.com/distribution/distribution/compare/v3.0.0...v3.1.0) --- updated-dependencies: - dependency-name: github.com/distribution/distribution/v3 dependency-version: 3.1.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 66 +++++++++++++------------- go.sum | 143 +++++++++++++++++++++++++++++---------------------------- 2 files changed, 107 insertions(+), 102 deletions(-) diff --git a/go.mod b/go.mod index 2ff16014a..53a5b9b69 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/ProtonMail/go-crypto v1.4.1 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/cyphar/filepath-securejoin v0.6.1 - github.com/distribution/distribution/v3 v3.0.0 + github.com/distribution/distribution/v3 v3.1.0 github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.19.0 @@ -60,7 +60,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect + github.com/bshuster-repo/logrus-logstash-hook v1.1.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect @@ -70,8 +70,8 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker-credential-helpers v0.8.2 // indirect - github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect + github.com/docker/docker-credential-helpers v0.9.5 // indirect + github.com/docker/go-events v0.0.0-20250808211157-605354379745 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect @@ -92,7 +92,7 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect github.com/huandu/xstrings v1.5.0 // indirect @@ -100,7 +100,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.4 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect @@ -123,50 +123,50 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/otlptranslator v1.0.0 // indirect - github.com/prometheus/procfs v0.19.2 // indirect + github.com/prometheus/procfs v0.20.1 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect github.com/redis/go-redis/v9 v9.7.3 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 // indirect - go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect - go.opentelemetry.io/otel v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.62.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 // indirect - go.opentelemetry.io/otel/log v0.16.0 // indirect - go.opentelemetry.io/otel/metric v1.40.0 // indirect - go.opentelemetry.io/otel/sdk v1.40.0 // indirect - go.opentelemetry.io/otel/sdk/log v0.16.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect - go.opentelemetry.io/otel/trace v1.40.0 // indirect + go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect + go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect + go.opentelemetry.io/otel v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect + go.opentelemetry.io/otel/log v0.18.0 // indirect + go.opentelemetry.io/otel/metric v1.42.0 // indirect + go.opentelemetry.io/otel/sdk v1.42.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.18.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect + go.opentelemetry.io/otel/trace v1.42.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/mod v0.33.0 // indirect golang.org/x/net v0.51.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.42.0 // indirect - golang.org/x/time v0.12.0 // indirect + golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.42.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/grpc v1.79.3 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect diff --git a/go.sum b/go.sum index 81c537cc6..59d8dbdea 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67 github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= +github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -34,8 +36,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bshuster-repo/logrus-logstash-hook v1.1.0 h1:o2FzZifLg+z/DN1OFmzTWzZZx/roaqt8IPZCIVco8r4= +github.com/bshuster-repo/logrus-logstash-hook v1.1.0/go.mod h1:Q2aXOe7rNuPgbBtPCOzYyWDvKX7+FpxE5sRdvcPoui0= github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= @@ -65,16 +67,16 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM= -github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU= +github.com/distribution/distribution/v3 v3.1.0 h1:u1v788HreKTLGdNY6s7px8Exgrs9mZ9UrCDjSrpCM8g= +github.com/distribution/distribution/v3 v3.1.0/go.mod h1:73BuF5/ziMHNVt7nnL1roYpH4Eg/FgUlKZm3WryIx/o= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= -github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= +github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= +github.com/docker/go-events v0.0.0-20250808211157-605354379745 h1:yOn6Ze6IbYI/KAw2lw/83ELYvZh6hvsygTVkD0dzMC4= +github.com/docker/go-events v0.0.0-20250808211157-605354379745/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a h1:UwSIFv5g5lIvbGgtf3tVwC7Ky9rmMFBp0RMs+6f6YqE= @@ -155,8 +157,8 @@ github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= @@ -177,8 +179,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -266,8 +268,8 @@ github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVR github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= -github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= @@ -288,8 +290,9 @@ github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepq github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= @@ -316,52 +319,54 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 h1:I/7S/yWobR3QHFLqHsJ8QOndoiFsj1VgHpQiq43KlUI= -go.opentelemetry.io/contrib/bridges/prometheus v0.65.0/go.mod h1:jPF6gn3y1E+nozCAEQj3c6NZ8KY+tvAgSVfvoOJUFac= -go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 h1:2gApdml7SznX9szEKFjKjM4qGcGSvAybYLBY319XG3g= -go.opentelemetry.io/contrib/exporters/autoexport v0.65.0/go.mod h1:0QqAGlbHXhmPYACG3n5hNzO5DnEqqtg4VcK5pr22RI0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= -go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= -go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 h1:ZVg+kCXxd9LtAaQNKBxAvJ5NpMf7LpvEr4MIZqb0TMQ= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0/go.mod h1:hh0tMeZ75CCXrHd9OXRYxTlCAdxcXioWHFIpYw2rZu8= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 h1:djrxvDxAe44mJUrKataUbOhCKhR3F8QCyWucO16hTQs= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0/go.mod h1:dt3nxpQEiSoKvfTVxp3TUg5fHPLhKtbcnN3Z1I1ePD0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 h1:NOyNnS19BF2SUDApbOKbDtWZ0IK7b8FJ2uAGdIWOGb0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0/go.mod h1:VL6EgVikRLcJa9ftukrHu/ZkkhFBSo1lzvdBC9CF1ss= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 h1:9y5sHvAxWzft1WQ4BwqcvA+IFVUJ1Ya75mSAUnFEVwE= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0/go.mod h1:eQqT90eR3X5Dbs1g9YSM30RavwLF725Ris5/XSXWvqE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= -go.opentelemetry.io/otel/exporters/prometheus v0.62.0 h1:krvC4JMfIOVdEuNPTtQ0ZjCiXrybhv+uOHMfHRmnvVo= -go.opentelemetry.io/otel/exporters/prometheus v0.62.0/go.mod h1:fgOE6FM/swEnsVQCqCnbOfRV4tOnWPg7bVeo4izBuhQ= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 h1:ivlbaajBWJqhcCPniDqDJmRwj4lc6sRT+dCAVKNmxlQ= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0/go.mod h1:u/G56dEKDDwXNCVLsbSrllB2o8pbtFLUC4HpR66r2dc= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= -go.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4= -go.opentelemetry.io/otel/log v0.16.0/go.mod h1:rWsmqNVTLIA8UnwYVOItjyEZDbKIkMxdQunsIhpUMes= -go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= -go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/log v0.16.0 h1:e/b4bdlQwC5fnGtG3dlXUrNOnP7c8YLVSpSfEBIkTnI= -go.opentelemetry.io/otel/sdk/log v0.16.0/go.mod h1:JKfP3T6ycy7QEuv3Hj8oKDy7KItrEkus8XJE6EoSzw4= -go.opentelemetry.io/otel/sdk/log/logtest v0.16.0 h1:/XVkpZ41rVRTP4DfMgYv1nEtNmf65XPPyAdqV90TMy4= -go.opentelemetry.io/otel/sdk/log/logtest v0.16.0/go.mod h1:iOOPgQr5MY9oac/F5W86mXdeyWZGleIx3uXO98X2R6Y= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= -go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXvpd09vxvDZsOjx11gjUqLk= +go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4= +go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8= +go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= +go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= +go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 h1:icqq3Z34UrEFk2u+HMhTtRsvo7Ues+eiJVjaJt62njs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0/go.mod h1:W2m8P+d5Wn5kipj4/xmbt9uMqezEKfBjzVJadfABSBE= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc= +go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs= +go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPfuTmWVkEu8J8qXaQwuV30pjCcAUvP8= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= +go.opentelemetry.io/otel/log v0.18.0 h1:XgeQIIBjZZrliksMEbcwMZefoOSMI1hdjiLEiiB0bAg= +go.opentelemetry.io/otel/log v0.18.0/go.mod h1:KEV1kad0NofR3ycsiDH4Yjcoj0+8206I6Ox2QYFSNgI= +go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= +go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= +go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= +go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= +go.opentelemetry.io/otel/sdk/log v0.18.0 h1:n8OyZr7t7otkeTnPTbDNom6rW16TBYGtvyy2Gk6buQw= +go.opentelemetry.io/otel/sdk/log v0.18.0/go.mod h1:C0+wxkTwKpOCZLrlJ3pewPiiQwpzycPI/u6W0Z9fuYk= +go.opentelemetry.io/otel/sdk/log/logtest v0.18.0 h1:l3mYuPsuBx6UKE47BVcPrZoZ0q/KER57vbj2qkgDLXA= +go.opentelemetry.io/otel/sdk/log/logtest v0.18.0/go.mod h1:7cHtiVJpZebB3wybTa4NG+FUo5NPe3PROz1FqB0+qdw= +go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= +go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= +go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= +go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -400,8 +405,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -416,11 +421,11 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -448,8 +453,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -461,10 +466,10 @@ golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4= +google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= From de58531ca7ff557342acaa2c906082e58521ef47 Mon Sep 17 00:00:00 2001 From: Anmol Virdi Date: Tue, 7 Apr 2026 01:23:50 +0530 Subject: [PATCH 250/571] Minor nit: fix import instructions to comply with canonical import paths Signed-off-by: Anmol Virdi --- internal/chart/v3/util/doc.go | 2 +- pkg/chart/v2/util/doc.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/chart/v3/util/doc.go b/internal/chart/v3/util/doc.go index 002d5babc..dc5a07462 100644 --- a/internal/chart/v3/util/doc.go +++ b/internal/chart/v3/util/doc.go @@ -42,4 +42,4 @@ into a Chart. When creating charts in memory, use the 'helm.sh/helm/pkg/chart' package directly. */ -package util // import chartutil "helm.sh/helm/v4/internal/chart/v3/util" +package util // import "helm.sh/helm/v4/internal/chart/v3/util" diff --git a/pkg/chart/v2/util/doc.go b/pkg/chart/v2/util/doc.go index 141062074..ed741a83d 100644 --- a/pkg/chart/v2/util/doc.go +++ b/pkg/chart/v2/util/doc.go @@ -42,4 +42,4 @@ into a Chart. When creating charts in memory, use the 'helm.sh/helm/pkg/chart' package directly. */ -package util // import chartutil "helm.sh/helm/v4/pkg/chart/v2/util" +package util // import "helm.sh/helm/v4/pkg/chart/v2/util" From 08dea9c140084b5d9fecb59a45a05f417415b591 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 21:40:22 +0000 Subject: [PATCH 251/571] chore(deps): bump github.com/distribution/distribution/v3 Bumps [github.com/distribution/distribution/v3](https://github.com/distribution/distribution) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/distribution/distribution/releases) - [Commits](https://github.com/distribution/distribution/compare/v3.0.0...v3.1.0) --- updated-dependencies: - dependency-name: github.com/distribution/distribution/v3 dependency-version: 3.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 66 +++++++++++++------------- go.sum | 143 +++++++++++++++++++++++++++++---------------------------- 2 files changed, 107 insertions(+), 102 deletions(-) diff --git a/go.mod b/go.mod index 2ff16014a..53a5b9b69 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/ProtonMail/go-crypto v1.4.1 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/cyphar/filepath-securejoin v0.6.1 - github.com/distribution/distribution/v3 v3.0.0 + github.com/distribution/distribution/v3 v3.1.0 github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.19.0 @@ -60,7 +60,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect + github.com/bshuster-repo/logrus-logstash-hook v1.1.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect @@ -70,8 +70,8 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker-credential-helpers v0.8.2 // indirect - github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect + github.com/docker/docker-credential-helpers v0.9.5 // indirect + github.com/docker/go-events v0.0.0-20250808211157-605354379745 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect @@ -92,7 +92,7 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect github.com/huandu/xstrings v1.5.0 // indirect @@ -100,7 +100,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.4 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect @@ -123,50 +123,50 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/otlptranslator v1.0.0 // indirect - github.com/prometheus/procfs v0.19.2 // indirect + github.com/prometheus/procfs v0.20.1 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect github.com/redis/go-redis/v9 v9.7.3 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 // indirect - go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect - go.opentelemetry.io/otel v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.62.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 // indirect - go.opentelemetry.io/otel/log v0.16.0 // indirect - go.opentelemetry.io/otel/metric v1.40.0 // indirect - go.opentelemetry.io/otel/sdk v1.40.0 // indirect - go.opentelemetry.io/otel/sdk/log v0.16.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect - go.opentelemetry.io/otel/trace v1.40.0 // indirect + go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect + go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect + go.opentelemetry.io/otel v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect + go.opentelemetry.io/otel/log v0.18.0 // indirect + go.opentelemetry.io/otel/metric v1.42.0 // indirect + go.opentelemetry.io/otel/sdk v1.42.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.18.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect + go.opentelemetry.io/otel/trace v1.42.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/mod v0.33.0 // indirect golang.org/x/net v0.51.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.42.0 // indirect - golang.org/x/time v0.12.0 // indirect + golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.42.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/grpc v1.79.3 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect diff --git a/go.sum b/go.sum index 81c537cc6..59d8dbdea 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67 github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= +github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -34,8 +36,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bshuster-repo/logrus-logstash-hook v1.1.0 h1:o2FzZifLg+z/DN1OFmzTWzZZx/roaqt8IPZCIVco8r4= +github.com/bshuster-repo/logrus-logstash-hook v1.1.0/go.mod h1:Q2aXOe7rNuPgbBtPCOzYyWDvKX7+FpxE5sRdvcPoui0= github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= @@ -65,16 +67,16 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM= -github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU= +github.com/distribution/distribution/v3 v3.1.0 h1:u1v788HreKTLGdNY6s7px8Exgrs9mZ9UrCDjSrpCM8g= +github.com/distribution/distribution/v3 v3.1.0/go.mod h1:73BuF5/ziMHNVt7nnL1roYpH4Eg/FgUlKZm3WryIx/o= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= -github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= +github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= +github.com/docker/go-events v0.0.0-20250808211157-605354379745 h1:yOn6Ze6IbYI/KAw2lw/83ELYvZh6hvsygTVkD0dzMC4= +github.com/docker/go-events v0.0.0-20250808211157-605354379745/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a h1:UwSIFv5g5lIvbGgtf3tVwC7Ky9rmMFBp0RMs+6f6YqE= @@ -155,8 +157,8 @@ github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= @@ -177,8 +179,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -266,8 +268,8 @@ github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVR github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= -github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= @@ -288,8 +290,9 @@ github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepq github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= @@ -316,52 +319,54 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 h1:I/7S/yWobR3QHFLqHsJ8QOndoiFsj1VgHpQiq43KlUI= -go.opentelemetry.io/contrib/bridges/prometheus v0.65.0/go.mod h1:jPF6gn3y1E+nozCAEQj3c6NZ8KY+tvAgSVfvoOJUFac= -go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 h1:2gApdml7SznX9szEKFjKjM4qGcGSvAybYLBY319XG3g= -go.opentelemetry.io/contrib/exporters/autoexport v0.65.0/go.mod h1:0QqAGlbHXhmPYACG3n5hNzO5DnEqqtg4VcK5pr22RI0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= -go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= -go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 h1:ZVg+kCXxd9LtAaQNKBxAvJ5NpMf7LpvEr4MIZqb0TMQ= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0/go.mod h1:hh0tMeZ75CCXrHd9OXRYxTlCAdxcXioWHFIpYw2rZu8= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 h1:djrxvDxAe44mJUrKataUbOhCKhR3F8QCyWucO16hTQs= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0/go.mod h1:dt3nxpQEiSoKvfTVxp3TUg5fHPLhKtbcnN3Z1I1ePD0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 h1:NOyNnS19BF2SUDApbOKbDtWZ0IK7b8FJ2uAGdIWOGb0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0/go.mod h1:VL6EgVikRLcJa9ftukrHu/ZkkhFBSo1lzvdBC9CF1ss= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 h1:9y5sHvAxWzft1WQ4BwqcvA+IFVUJ1Ya75mSAUnFEVwE= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0/go.mod h1:eQqT90eR3X5Dbs1g9YSM30RavwLF725Ris5/XSXWvqE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= -go.opentelemetry.io/otel/exporters/prometheus v0.62.0 h1:krvC4JMfIOVdEuNPTtQ0ZjCiXrybhv+uOHMfHRmnvVo= -go.opentelemetry.io/otel/exporters/prometheus v0.62.0/go.mod h1:fgOE6FM/swEnsVQCqCnbOfRV4tOnWPg7bVeo4izBuhQ= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 h1:ivlbaajBWJqhcCPniDqDJmRwj4lc6sRT+dCAVKNmxlQ= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0/go.mod h1:u/G56dEKDDwXNCVLsbSrllB2o8pbtFLUC4HpR66r2dc= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= -go.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4= -go.opentelemetry.io/otel/log v0.16.0/go.mod h1:rWsmqNVTLIA8UnwYVOItjyEZDbKIkMxdQunsIhpUMes= -go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= -go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/log v0.16.0 h1:e/b4bdlQwC5fnGtG3dlXUrNOnP7c8YLVSpSfEBIkTnI= -go.opentelemetry.io/otel/sdk/log v0.16.0/go.mod h1:JKfP3T6ycy7QEuv3Hj8oKDy7KItrEkus8XJE6EoSzw4= -go.opentelemetry.io/otel/sdk/log/logtest v0.16.0 h1:/XVkpZ41rVRTP4DfMgYv1nEtNmf65XPPyAdqV90TMy4= -go.opentelemetry.io/otel/sdk/log/logtest v0.16.0/go.mod h1:iOOPgQr5MY9oac/F5W86mXdeyWZGleIx3uXO98X2R6Y= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= -go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXvpd09vxvDZsOjx11gjUqLk= +go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4= +go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8= +go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= +go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= +go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 h1:icqq3Z34UrEFk2u+HMhTtRsvo7Ues+eiJVjaJt62njs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0/go.mod h1:W2m8P+d5Wn5kipj4/xmbt9uMqezEKfBjzVJadfABSBE= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc= +go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs= +go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPfuTmWVkEu8J8qXaQwuV30pjCcAUvP8= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= +go.opentelemetry.io/otel/log v0.18.0 h1:XgeQIIBjZZrliksMEbcwMZefoOSMI1hdjiLEiiB0bAg= +go.opentelemetry.io/otel/log v0.18.0/go.mod h1:KEV1kad0NofR3ycsiDH4Yjcoj0+8206I6Ox2QYFSNgI= +go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= +go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= +go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= +go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= +go.opentelemetry.io/otel/sdk/log v0.18.0 h1:n8OyZr7t7otkeTnPTbDNom6rW16TBYGtvyy2Gk6buQw= +go.opentelemetry.io/otel/sdk/log v0.18.0/go.mod h1:C0+wxkTwKpOCZLrlJ3pewPiiQwpzycPI/u6W0Z9fuYk= +go.opentelemetry.io/otel/sdk/log/logtest v0.18.0 h1:l3mYuPsuBx6UKE47BVcPrZoZ0q/KER57vbj2qkgDLXA= +go.opentelemetry.io/otel/sdk/log/logtest v0.18.0/go.mod h1:7cHtiVJpZebB3wybTa4NG+FUo5NPe3PROz1FqB0+qdw= +go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= +go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= +go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= +go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -400,8 +405,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -416,11 +421,11 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -448,8 +453,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -461,10 +466,10 @@ golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4= +google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= From 4c0d21f53f2ca78b525e31dbbf9cc9cfb818a2e3 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Tue, 7 Apr 2026 04:26:12 -0600 Subject: [PATCH 252/571] test(kube): fix flaky WaitForDelete timing in status wait tests TestMethodContextOverridesGeneralContext/WaitForDelete used a 1s timeout with a 500ms deletion delay, leaving only ~500ms for the fake watcher to propagate the delete event. On loaded CI runners this window is too tight and causes intermittent failures. Increase the timeout to 5s and reduce the deletion delay to 100ms so there is ample headroom. Apply the same deletion-delay reduction to TestStatusWaitForDelete which shares the same pattern. Signed-off-by: Terry Howe --- pkg/kube/statuswait_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index 0639e07fc..3ce6b0fd6 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -317,7 +317,7 @@ func TestStatusWaitForDelete(t *testing.T) { t.Parallel() c := newTestClient(t) timeout := time.Second - timeUntilPodDelete := time.Millisecond * 500 + timeUntilPodDelete := time.Millisecond * 100 fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) fakeMapper := testutil.NewFakeRESTMapper( v1.SchemeGroupVersion.WithKind("Pod"), @@ -1680,8 +1680,8 @@ func TestMethodContextOverridesGeneralContext(t *testing.T) { t.Run("method-specific context overrides general context for WaitForDelete", func(t *testing.T) { t.Parallel() c := newTestClient(t) - timeout := time.Second - timeUntilPodDelete := time.Millisecond * 500 + timeout := 5 * time.Second + timeUntilPodDelete := time.Millisecond * 100 fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) fakeMapper := testutil.NewFakeRESTMapper( v1.SchemeGroupVersion.WithKind("Pod"), From a7f84439aacd3864b40055b60a3c3e54292d1646 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Tue, 7 Apr 2026 08:23:39 -0600 Subject: [PATCH 253/571] test(kube): fix flaky WaitForDelete test by avoiding informer sync race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fix (increasing timeout / reducing deletion delay) did not work because the flakiness is not a timing problem at all. Root cause: fluxcd/cli-utils HasSynced() returns true after the initial list item is *popped* from DeltaFIFO, which is before AddFunc delivers the ResourceUpdateEvent to the collector. This creates a race where the SyncEvent can arrive at the statusObserver *before* the pod's Current status is recorded. When that happens: - statusObserver sees pod as Unknown - Unknown is skipped for WaitForDelete (by design, to handle resources that were already deleted before watching started) - AggregateStatus([], NotFoundStatus) == NotFoundStatus → cancel() - The watch context is cancelled before DeleteFunc can fire - Final check: pod still Current → error The test intent is to verify that waitForDeleteCtx (not the cancelled generalCtx) is selected. A non-existent resource satisfies this: - With waitForDeleteCtx=Background(): informer syncs with empty list → Unknown → cancel → success ✓ - With generalCtx (cancelled, wrong): context immediately done → ctx.Err() appended → error returned ✓ Remove the goroutine-based deletion and the pod creation to eliminate the race while preserving the context-selection assertion. Signed-off-by: Terry Howe --- pkg/kube/statuswait_test.go | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index 3ce6b0fd6..73a424720 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -317,7 +317,7 @@ func TestStatusWaitForDelete(t *testing.T) { t.Parallel() c := newTestClient(t) timeout := time.Second - timeUntilPodDelete := time.Millisecond * 100 + timeUntilPodDelete := time.Millisecond * 500 fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) fakeMapper := testutil.NewFakeRESTMapper( v1.SchemeGroupVersion.WithKind("Pod"), @@ -1680,8 +1680,6 @@ func TestMethodContextOverridesGeneralContext(t *testing.T) { t.Run("method-specific context overrides general context for WaitForDelete", func(t *testing.T) { t.Parallel() c := newTestClient(t) - timeout := 5 * time.Second - timeUntilPodDelete := time.Millisecond * 100 fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme) fakeMapper := testutil.NewFakeRESTMapper( v1.SchemeGroupVersion.WithKind("Pod"), @@ -1698,27 +1696,14 @@ func TestMethodContextOverridesGeneralContext(t *testing.T) { waitForDeleteCtx: context.Background(), // Not cancelled - should be used } + // Use a non-existent resource: WaitForDelete should return immediately since + // the pod is already in the desired "deleted" state. + // This also validates context selection: if generalCtx (cancelled) were + // incorrectly used instead of waitForDeleteCtx, the watch context would be + // immediately cancelled and the call would return a context error. objs := getRuntimeObjFromManifests(t, []string{podCurrentManifest}) - for _, obj := range objs { - u := obj.(*unstructured.Unstructured) - gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) - } - - // Schedule deletion - for _, obj := range objs { - u := obj.(*unstructured.Unstructured) - gvr := getGVR(t, fakeMapper, u) - go func(gvr schema.GroupVersionResource, u *unstructured.Unstructured) { - time.Sleep(timeUntilPodDelete) - err := fakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName()) - assert.NoError(t, err) - }(gvr, u) - } - resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := sw.WaitForDelete(resourceList, timeout) + err := sw.WaitForDelete(resourceList, time.Second) // Should succeed because method context is used and it's not cancelled assert.NoError(t, err) }) From 6ebfb29dbf006ce78e9af8878008bda3578dcd3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 19:31:43 +0000 Subject: [PATCH 254/571] chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp Bumps [go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp](https://github.com/open-telemetry/opentelemetry-go) from 0.18.0 to 0.19.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v0.18.0...v0.19.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp dependency-version: 0.19.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 26 ++++++++++++------------- go.sum | 60 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/go.mod b/go.mod index 53a5b9b69..f99b239d6 100644 --- a/go.mod +++ b/go.mod @@ -138,9 +138,9 @@ require ( go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect - go.opentelemetry.io/otel v1.42.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect @@ -150,24 +150,24 @@ require ( go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect - go.opentelemetry.io/otel/log v0.18.0 // indirect - go.opentelemetry.io/otel/metric v1.42.0 // indirect - go.opentelemetry.io/otel/sdk v1.42.0 // indirect - go.opentelemetry.io/otel/sdk/log v0.18.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect - go.opentelemetry.io/otel/trace v1.42.0 // indirect - go.opentelemetry.io/proto/otlp v1.9.0 // indirect + go.opentelemetry.io/otel/log v0.19.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/mod v0.33.0 // indirect - golang.org/x/net v0.51.0 // indirect + golang.org/x/net v0.52.0 // indirect golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.42.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect - google.golang.org/grpc v1.79.3 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/grpc v1.80.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 59d8dbdea..d28245ed0 100644 --- a/go.sum +++ b/go.sum @@ -329,12 +329,12 @@ go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9a go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= -go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= -go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 h1:icqq3Z34UrEFk2u+HMhTtRsvo7Ues+eiJVjaJt62njs= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0/go.mod h1:W2m8P+d5Wn5kipj4/xmbt9uMqezEKfBjzVJadfABSBE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0/go.mod h1:ji9vId85hMxqfvICA0Jt8JqEdrXaAkcpkI9HPXya0ro= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA= @@ -353,22 +353,22 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPf go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= -go.opentelemetry.io/otel/log v0.18.0 h1:XgeQIIBjZZrliksMEbcwMZefoOSMI1hdjiLEiiB0bAg= -go.opentelemetry.io/otel/log v0.18.0/go.mod h1:KEV1kad0NofR3ycsiDH4Yjcoj0+8206I6Ox2QYFSNgI= -go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= -go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= -go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= -go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= -go.opentelemetry.io/otel/sdk/log v0.18.0 h1:n8OyZr7t7otkeTnPTbDNom6rW16TBYGtvyy2Gk6buQw= -go.opentelemetry.io/otel/sdk/log v0.18.0/go.mod h1:C0+wxkTwKpOCZLrlJ3pewPiiQwpzycPI/u6W0Z9fuYk= -go.opentelemetry.io/otel/sdk/log/logtest v0.18.0 h1:l3mYuPsuBx6UKE47BVcPrZoZ0q/KER57vbj2qkgDLXA= -go.opentelemetry.io/otel/sdk/log/logtest v0.18.0/go.mod h1:7cHtiVJpZebB3wybTa4NG+FUo5NPe3PROz1FqB0+qdw= -go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= -go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= -go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= -go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= -go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= -go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= +go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= +go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= +go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk= +go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -403,8 +403,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= -golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -464,14 +464,14 @@ golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4= -google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= +google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From b0cec589f50a7e16d942ad3385598a6dda2b0a20 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 19:54:08 +0000 Subject: [PATCH 255/571] chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp](https://github.com/open-telemetry/opentelemetry-go) from 1.42.0 to 1.43.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.42.0...v1.43.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp dependency-version: 1.43.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index f99b239d6..0d8f455f5 100644 --- a/go.mod +++ b/go.mod @@ -143,9 +143,9 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect diff --git a/go.sum b/go.sum index d28245ed0..325a3dfce 100644 --- a/go.sum +++ b/go.sum @@ -339,12 +339,12 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdK go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs= go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s= From 998466cfcfee189ce7e3df5be8ffe79ed5f1f097 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 20:04:20 +0000 Subject: [PATCH 256/571] chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp Bumps [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp](https://github.com/open-telemetry/opentelemetry-go) from 1.42.0 to 1.43.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.42.0...v1.43.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp dependency-version: 1.43.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0d8f455f5..f6c13b0ab 100644 --- a/go.mod +++ b/go.mod @@ -142,7 +142,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect diff --git a/go.sum b/go.sum index 325a3dfce..d436c3462 100644 --- a/go.sum +++ b/go.sum @@ -337,8 +337,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0/go.mod h1:ji9vId85hMxqfvICA0Jt8JqEdrXaAkcpkI9HPXya0ro= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= From c8c5dfad630cd7b238236c619c466488a547725c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 21:34:33 +0000 Subject: [PATCH 257/571] chore(deps): bump github.com/fluxcd/cli-utils Bumps [github.com/fluxcd/cli-utils](https://github.com/fluxcd/cli-utils) from 0.37.2-flux.1 to 1.0.0. - [Release notes](https://github.com/fluxcd/cli-utils/releases) - [Commits](https://github.com/fluxcd/cli-utils/compare/v0.37.2-flux.1...v1.0.0) --- updated-dependencies: - dependency-name: github.com/fluxcd/cli-utils dependency-version: 1.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- go.mod | 24 ++++++++++++------------ go.sum | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/go.mod b/go.mod index f6c13b0ab..8afc66197 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.19.0 - github.com/fluxcd/cli-utils v0.37.2-flux.1 + github.com/fluxcd/cli-utils v1.0.0 github.com/foxcpp/go-mockdns v1.2.0 github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.13.0 @@ -39,14 +39,14 @@ require ( golang.org/x/term v0.41.0 golang.org/x/text v0.35.0 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.35.1 - k8s.io/apiextensions-apiserver v0.35.1 - k8s.io/apimachinery v0.35.1 - k8s.io/apiserver v0.35.1 - k8s.io/cli-runtime v0.35.1 - k8s.io/client-go v0.35.1 + k8s.io/api v0.35.3 + k8s.io/apiextensions-apiserver v0.35.3 + k8s.io/apimachinery v0.35.3 + k8s.io/apiserver v0.35.3 + k8s.io/cli-runtime v0.35.3 + k8s.io/client-go v0.35.3 k8s.io/klog/v2 v2.130.1 - k8s.io/kubectl v0.35.1 + k8s.io/kubectl v0.35.3 oras.land/oras-go/v2 v2.6.0 sigs.k8s.io/controller-runtime v0.23.3 sigs.k8s.io/kustomize/kyaml v0.21.1 @@ -116,7 +116,7 @@ require ( github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/gomega v1.39.0 // indirect + github.com/onsi/gomega v1.39.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect @@ -160,10 +160,10 @@ require ( go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/mod v0.33.0 // indirect golang.org/x/net v0.52.0 // indirect - golang.org/x/oauth2 v0.35.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.42.0 // indirect - golang.org/x/time v0.14.0 // indirect + golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.42.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect @@ -172,7 +172,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/component-base v0.35.1 // indirect + k8s.io/component-base v0.35.3 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect diff --git a/go.sum b/go.sum index d436c3462..f4a70b25a 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/cli-utils v0.37.2-flux.1 h1:tQ588ghtRN+E+kHq415FddfqA9v4brn/1WWgrP6rQR0= -github.com/fluxcd/cli-utils v0.37.2-flux.1/go.mod h1:LcWSu1NYET8d8U7O326RhEm5JkQXCMK6ITu4G1CT02c= +github.com/fluxcd/cli-utils v1.0.0 h1:+luz8igR6dM5f7uHwkkMTECsl+jp0kR69POuV5aOoDs= +github.com/fluxcd/cli-utils v1.0.0/go.mod h1:ANTIXWLLsNmn5bMNxbyoY22rtwRSR/fbu+IFy756fs0= github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -236,8 +236,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= -github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -405,8 +405,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= -golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= -golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -453,8 +453,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -488,26 +488,26 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q= -k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM= -k8s.io/apiextensions-apiserver v0.35.1 h1:p5vvALkknlOcAqARwjS20kJffgzHqwyQRM8vHLwgU7w= -k8s.io/apiextensions-apiserver v0.35.1/go.mod h1:2CN4fe1GZ3HMe4wBr25qXyJnJyZaquy4nNlNmb3R7AQ= -k8s.io/apimachinery v0.35.1 h1:yxO6gV555P1YV0SANtnTjXYfiivaTPvCTKX6w6qdDsU= -k8s.io/apimachinery v0.35.1/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -k8s.io/apiserver v0.35.1 h1:potxdhhTL4i6AYAa2QCwtlhtB1eCdWQFvJV6fXgJzxs= -k8s.io/apiserver v0.35.1/go.mod h1:BiL6Dd3A2I/0lBnteXfWmCFobHM39vt5+hJQd7Lbpi4= -k8s.io/cli-runtime v0.35.1 h1:uKcXFe8J7AMAM4Gm2JDK4mp198dBEq2nyeYtO+JfGJE= -k8s.io/cli-runtime v0.35.1/go.mod h1:55/hiXIq1C8qIJ3WBrWxEwDLdHQYhBNRdZOz9f7yvTw= -k8s.io/client-go v0.35.1 h1:+eSfZHwuo/I19PaSxqumjqZ9l5XiTEKbIaJ+j1wLcLM= -k8s.io/client-go v0.35.1/go.mod h1:1p1KxDt3a0ruRfc/pG4qT/3oHmUj1AhSHEcxNSGg+OA= -k8s.io/component-base v0.35.1 h1:XgvpRf4srp037QWfGBLFsYMUQJkE5yMa94UsJU7pmcE= -k8s.io/component-base v0.35.1/go.mod h1:HI/6jXlwkiOL5zL9bqA3en1Ygv60F03oEpnuU1G56Bs= +k8s.io/api v0.35.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ= +k8s.io/api v0.35.3/go.mod h1:9Y9tkBcFwKNq2sxwZTQh1Njh9qHl81D0As56tu42GA4= +k8s.io/apiextensions-apiserver v0.35.3 h1:2fQUhEO7P17sijylbdwt0nBdXP0TvHrHj0KeqHD8FiU= +k8s.io/apiextensions-apiserver v0.35.3/go.mod h1:tK4Kz58ykRpwAEkXUb634HD1ZAegEElktz/B3jgETd8= +k8s.io/apimachinery v0.35.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8= +k8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/apiserver v0.35.3 h1:D2eIcfJ05hEAEewoSDg+05e0aSRwx8Y4Agvd/wiomUI= +k8s.io/apiserver v0.35.3/go.mod h1:JI0n9bHYzSgIxgIrfe21dbduJ9NHzKJ6RchcsmIKWKY= +k8s.io/cli-runtime v0.35.3 h1:UZq4ipNimtzBmhN7PPKbfAdqo8quK0H0UdGl6qAQnqI= +k8s.io/cli-runtime v0.35.3/go.mod h1:O7MUmCqcKSd5xI+O5X7/pRkB5l0O2NIhOdUVwbHLXu4= +k8s.io/client-go v0.35.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg= +k8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c= +k8s.io/component-base v0.35.3 h1:mbKbzoIMy7JDWS/wqZobYW1JDVRn/RKRaoMQHP9c4P0= +k8s.io/component-base v0.35.3/go.mod h1:IZ8LEG30kPN4Et5NeC7vjNv5aU73ku5MS15iZyvyMYk= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/kubectl v0.35.1 h1:zP3Er8C5i1dcAFUMh9Eva0kVvZHptXIn/+8NtRWMxwg= -k8s.io/kubectl v0.35.1/go.mod h1:cQ2uAPs5IO/kx8R5s5J3Ihv3VCYwrx0obCXum0CvnXo= +k8s.io/kubectl v0.35.3 h1:1KqSYXk/sodU7VeDvK6atX2kAGUZd2QTeR5K7Hb9r9w= +k8s.io/kubectl v0.35.3/go.mod h1:GPHxZqRe+u/i3gTBoVQHeIyq2NilfNPj9hDWeuN3x5s= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= From 586eb57338d848e65686a3a9616e2776e87cfd1e Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Fri, 6 Mar 2026 07:40:11 -0800 Subject: [PATCH 258/571] fix: Plugin missing provenance bypass Signed-off-by: George Jenkins --- internal/plugin/installer/installer.go | 33 +++++------ .../plugin/installer/verification_test.go | 58 ++++++++++++------- pkg/cmd/plugin_install.go | 6 +- 3 files changed, 55 insertions(+), 42 deletions(-) diff --git a/internal/plugin/installer/installer.go b/internal/plugin/installer/installer.go index 1ab14e813..943123a7e 100644 --- a/internal/plugin/installer/installer.go +++ b/internal/plugin/installer/installer.go @@ -98,24 +98,23 @@ func InstallWithOptions(i Installer, opts Options) (*VerificationResult, error) // Check if provenance data exists if len(provData) == 0 { - // No .prov file found - emit warning but continue installation - fmt.Fprint(os.Stderr, "WARNING: No provenance file found for plugin. Plugin is not signed and cannot be verified.\n") - } else { - // Provenance data exists - verify the plugin - verification, err := plugin.VerifyPlugin(archiveData, provData, filename, opts.Keyring) - if err != nil { - return nil, fmt.Errorf("plugin verification failed: %w", err) - } + return nil, fmt.Errorf("plugin verification failed: no provenance file (.prov) found") + } - // Collect verification info - result = &VerificationResult{ - SignedBy: make([]string, 0), - Fingerprint: fmt.Sprintf("%X", verification.SignedBy.PrimaryKey.Fingerprint), - FileHash: verification.FileHash, - } - for name := range verification.SignedBy.Identities { - result.SignedBy = append(result.SignedBy, name) - } + // Provenance data exists - verify the plugin + verification, err := plugin.VerifyPlugin(archiveData, provData, filename, opts.Keyring) + if err != nil { + return nil, fmt.Errorf("plugin verification failed: %w", err) + } + + // Collect verification info + result = &VerificationResult{ + SignedBy: make([]string, 0), + Fingerprint: fmt.Sprintf("%X", verification.SignedBy.PrimaryKey.Fingerprint), + FileHash: verification.FileHash, + } + for name := range verification.SignedBy.Identities { + result.SignedBy = append(result.SignedBy, name) } } diff --git a/internal/plugin/installer/verification_test.go b/internal/plugin/installer/verification_test.go index 390624827..79bc242cc 100644 --- a/internal/plugin/installer/verification_test.go +++ b/internal/plugin/installer/verification_test.go @@ -16,10 +16,8 @@ limitations under the License. package installer import ( - "bytes" "crypto/sha256" "fmt" - "io" "os" "path/filepath" "strings" @@ -44,33 +42,49 @@ func TestInstallWithOptions_VerifyMissingProvenance(t *testing.T) { } defer os.RemoveAll(installer.Path()) - // Capture stderr to check warning message - oldStderr := os.Stderr - r, w, _ := os.Pipe() - os.Stderr = w - - // Install with verification enabled (should warn but succeed) + // Install with verification enabled should fail when .prov is missing result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: "dummy"}) - // Restore stderr and read captured output - w.Close() - os.Stderr = oldStderr - var buf bytes.Buffer - io.Copy(&buf, r) - output := buf.String() - - // Should succeed with nil result (no verification performed) - if err != nil { - t.Fatalf("Expected installation to succeed despite missing .prov file, got error: %v", err) + // Should fail with a missing provenance error + if err == nil { + t.Fatal("Expected installation to fail when .prov file is missing and verification is enabled") + } + if !strings.Contains(err.Error(), "no provenance file") { + t.Errorf("Expected 'no provenance file' in error message, got: %v", err) } if result != nil { t.Errorf("Expected nil verification result when .prov file is missing, got: %+v", result) } - // Should contain warning message - expectedWarning := "WARNING: No provenance file found for plugin" - if !strings.Contains(output, expectedWarning) { - t.Errorf("Expected warning message '%s' in output, got: %s", expectedWarning, output) + // Plugin should NOT be installed + if _, err := os.Stat(installer.Path()); !os.IsNotExist(err) { + t.Errorf("Plugin should not be installed when verification fails due to missing .prov") + } +} + +func TestInstallWithOptions_NoVerifyMissingProvenance(t *testing.T) { + ensure.HelmHome(t) + + // Create a temporary plugin tarball without .prov file + pluginDir := createTestPluginDir(t) + pluginTgz := createTarballFromPluginDir(t, pluginDir) + defer os.Remove(pluginTgz) + + // Create local installer + installer, err := NewLocalInstaller(pluginTgz) + if err != nil { + t.Fatalf("Failed to create installer: %v", err) + } + defer os.RemoveAll(installer.Path()) + + // Install with verification explicitly disabled should succeed without .prov + result, err := InstallWithOptions(installer, Options{Verify: false}) + + if err != nil { + t.Fatalf("Expected installation to succeed with --verify=false, got error: %v", err) + } + if result != nil { + t.Errorf("Expected nil verification result when verification is disabled, got: %+v", result) } // Plugin should be installed diff --git a/pkg/cmd/plugin_install.go b/pkg/cmd/plugin_install.go index 2ce8ad7b0..c248ed818 100644 --- a/pkg/cmd/plugin_install.go +++ b/pkg/cmd/plugin_install.go @@ -51,11 +51,11 @@ const pluginInstallDesc = ` This command allows you to install a plugin from a url to a VCS repo or a local path. By default, plugin signatures are verified before installation when installing from -tarballs (.tgz or .tar.gz). This requires a corresponding .prov file to be available -alongside the tarball. +tarballs (.tgz or .tar.gz). A corresponding .prov file must be available alongside +the tarball; installation will fail if it is missing or invalid. For local development, plugins installed from local directories are automatically treated as "local dev" and do not require signatures. -Use --verify=false to skip signature verification for remote plugins. +Use --verify=false to explicitly skip signature verification (NOT recommended). ` func newPluginInstallCmd(out io.Writer) *cobra.Command { From 087736b66e97393ccaa0bdf1e5df13dcc9d88340 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Wed, 8 Apr 2026 22:00:58 -0700 Subject: [PATCH 259/571] fix: unnecessary-format lint issues from merge Signed-off-by: George Jenkins --- internal/plugin/installer/installer.go | 2 +- internal/plugin/installer/verification_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/plugin/installer/installer.go b/internal/plugin/installer/installer.go index 943123a7e..69a797ad9 100644 --- a/internal/plugin/installer/installer.go +++ b/internal/plugin/installer/installer.go @@ -98,7 +98,7 @@ func InstallWithOptions(i Installer, opts Options) (*VerificationResult, error) // Check if provenance data exists if len(provData) == 0 { - return nil, fmt.Errorf("plugin verification failed: no provenance file (.prov) found") + return nil, errors.New("plugin verification failed: no provenance file (.prov) found") } // Provenance data exists - verify the plugin diff --git a/internal/plugin/installer/verification_test.go b/internal/plugin/installer/verification_test.go index 79bc242cc..e05cda7fd 100644 --- a/internal/plugin/installer/verification_test.go +++ b/internal/plugin/installer/verification_test.go @@ -58,7 +58,7 @@ func TestInstallWithOptions_VerifyMissingProvenance(t *testing.T) { // Plugin should NOT be installed if _, err := os.Stat(installer.Path()); !os.IsNotExist(err) { - t.Errorf("Plugin should not be installed when verification fails due to missing .prov") + t.Error("Plugin should not be installed when verification fails due to missing .prov") } } From bd5027a9cf07993d7bfe4b60702b1a489fe8783e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:22:52 +0000 Subject: [PATCH 260/571] chore(deps): bump github.com/lib/pq from 1.12.2 to 1.12.3 Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.12.2 to 1.12.3. - [Release notes](https://github.com/lib/pq/releases) - [Changelog](https://github.com/lib/pq/blob/master/CHANGELOG.md) - [Commits](https://github.com/lib/pq/compare/v1.12.2...v1.12.3) --- updated-dependencies: - dependency-name: github.com/lib/pq dependency-version: 1.12.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f6c13b0ab..b2c428042 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/gofrs/flock v0.13.0 github.com/gosuri/uitable v0.0.4 github.com/jmoiron/sqlx v1.4.0 - github.com/lib/pq v1.12.2 + github.com/lib/pq v1.12.3 github.com/mattn/go-shellwords v1.0.12 github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 diff --git a/go.sum b/go.sum index d436c3462..6a7d84297 100644 --- a/go.sum +++ b/go.sum @@ -194,8 +194,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.12.2 h1:ajJNv84limnK3aPbDIhLtcjrUbqAw/5XNdkuI6KNe/Q= -github.com/lib/pq v1.12.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= +github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= From 213c869a988f2c7390c65673e3d677970d6220fd Mon Sep 17 00:00:00 2001 From: Sumit Solanki Date: Thu, 9 Apr 2026 22:46:41 +0530 Subject: [PATCH 261/571] refactor(cli): share RetryingRoundTripper via pkg/kubeenv Move implementation to pkg/kubeenv per review; kube.RetryingRoundTripper remains a type alias for API compatibility. pkg/cli uses kubeenv only. Signed-off-by: Sumit Solanki --- pkg/cli/environment.go | 3 +- pkg/cli/roundtripper_test.go | 161 --------------------- pkg/kube/roundtripper.go | 66 +-------- pkg/{cli => kubeenv}/roundtripper.go | 18 ++- pkg/{kube => kubeenv}/roundtripper_test.go | 2 +- 5 files changed, 18 insertions(+), 232 deletions(-) delete mode 100644 pkg/cli/roundtripper_test.go rename pkg/{cli => kubeenv}/roundtripper.go (77%) rename pkg/{kube => kubeenv}/roundtripper_test.go (99%) diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 8faa628fc..45d773eb4 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -35,6 +35,7 @@ import ( "helm.sh/helm/v4/internal/version" "helm.sh/helm/v4/pkg/helmpath" + "helm.sh/helm/v4/pkg/kubeenv" ) // defaultMaxHistory sets the maximum number of releases to 0: unlimited @@ -133,7 +134,7 @@ func New() *EnvSettings { config.Burst = env.BurstLimit config.QPS = env.QPS config.Wrap(func(rt http.RoundTripper) http.RoundTripper { - return &retryingRoundTripper{wrapped: rt} + return &kubeenv.RetryingRoundTripper{Wrapped: rt} }) config.UserAgent = version.GetUserAgent() return config diff --git a/pkg/cli/roundtripper_test.go b/pkg/cli/roundtripper_test.go deleted file mode 100644 index 68fc075b0..000000000 --- a/pkg/cli/roundtripper_test.go +++ /dev/null @@ -1,161 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cli - -import ( - "encoding/json" - "errors" - "io" - "net/http" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -type fakeRoundTripper struct { - resp *http.Response - err error - calls int -} - -func (f *fakeRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) { - f.calls++ - return f.resp, f.err -} - -func newRespWithBody(statusCode int, contentType, body string) *http.Response { - return &http.Response{ - StatusCode: statusCode, - Header: http.Header{"Content-Type": []string{contentType}}, - Body: io.NopCloser(strings.NewReader(body)), - } -} - -func TestRetryingRoundTripper_RoundTrip(t *testing.T) { - marshalErr := func(code int, msg string) string { - b, _ := json.Marshal(kubernetesError{ - Code: code, - Message: msg, - }) - return string(b) - } - - tests := []struct { - name string - resp *http.Response - err error - expectedCalls int - expectedErr string - expectedCode int - }{ - { - name: "no retry, status < 500 returns response", - resp: newRespWithBody(200, "application/json", `{"message":"ok","code":200}`), - err: nil, - expectedCalls: 1, - expectedCode: 200, - }, - { - name: "error from wrapped RoundTripper propagates", - resp: nil, - err: errors.New("wrapped error"), - expectedCalls: 1, - expectedErr: "wrapped error", - }, - { - name: "no retry, content-type not application/json", - resp: newRespWithBody(500, "text/plain", "server error"), - err: nil, - expectedCalls: 1, - expectedCode: 500, - }, - { - name: "error reading body returns error", - resp: &http.Response{ - StatusCode: http.StatusInternalServerError, - Header: http.Header{"Content-Type": []string{"application/json"}}, - Body: &errReader{}, - }, - err: nil, - expectedCalls: 1, - expectedErr: "read error", - }, - { - name: "error decoding JSON returns error", - resp: newRespWithBody(500, "application/json", `invalid-json`), - err: nil, - expectedCalls: 1, - expectedErr: "invalid character", - }, - { - name: "retry on etcdserver leader changed message", - resp: newRespWithBody(500, "application/json", marshalErr(500, "some error etcdserver: leader changed")), - err: nil, - expectedCalls: 2, - expectedCode: 500, - }, - { - name: "retry on raft proposal dropped message", - resp: newRespWithBody(500, "application/json", marshalErr(500, "rpc error: code = Unknown desc = raft proposal dropped")), - err: nil, - expectedCalls: 2, - expectedCode: 500, - }, - { - name: "no retry on other error message", - resp: newRespWithBody(500, "application/json", marshalErr(500, "other server error")), - err: nil, - expectedCalls: 1, - expectedCode: 500, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fakeRT := &fakeRoundTripper{ - resp: tt.resp, - err: tt.err, - } - rt := retryingRoundTripper{ - wrapped: fakeRT, - } - req, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) - resp, err := rt.RoundTrip(req) - - if tt.expectedErr != "" { - assert.Error(t, err) - assert.Contains(t, err.Error(), tt.expectedErr) - return - } - assert.NoError(t, err) - - assert.Equal(t, tt.expectedCode, resp.StatusCode) - assert.Equal(t, tt.expectedCalls, fakeRT.calls) - }) - } -} - -type errReader struct{} - -func (e *errReader) Read(_ []byte) (int, error) { - return 0, errors.New("read error") -} - -func (e *errReader) Close() error { - return nil -} diff --git a/pkg/kube/roundtripper.go b/pkg/kube/roundtripper.go index 52cb5bad2..e13f2103a 100644 --- a/pkg/kube/roundtripper.go +++ b/pkg/kube/roundtripper.go @@ -16,65 +16,9 @@ limitations under the License. package kube -import ( - "bytes" - "encoding/json" - "io" - "net/http" - "strings" -) +import "helm.sh/helm/v4/pkg/kubeenv" -type RetryingRoundTripper struct { - Wrapped http.RoundTripper -} - -func (rt *RetryingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - return rt.roundTrip(req, 1, nil) -} - -func (rt *RetryingRoundTripper) roundTrip(req *http.Request, retry int, prevResp *http.Response) (*http.Response, error) { - if retry < 0 { - return prevResp, nil - } - resp, rtErr := rt.Wrapped.RoundTrip(req) - if rtErr != nil { - return resp, rtErr - } - if resp.StatusCode < 500 { - return resp, rtErr - } - if resp.Header.Get("content-type") != "application/json" { - return resp, rtErr - } - b, err := io.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - return resp, err - } - - var ke kubernetesError - r := bytes.NewReader(b) - err = json.NewDecoder(r).Decode(&ke) - r.Seek(0, io.SeekStart) - resp.Body = io.NopCloser(r) - if err != nil { - return resp, err - } - if ke.Code < 500 { - return resp, nil - } - // Matches messages like "etcdserver: leader changed" - if strings.HasSuffix(ke.Message, "etcdserver: leader changed") { - return rt.roundTrip(req, retry-1, resp) - } - // Matches messages like "rpc error: code = Unknown desc = raft proposal dropped" - if strings.HasSuffix(ke.Message, "raft proposal dropped") { - return rt.roundTrip(req, retry-1, resp) - } - return resp, nil -} - -type kubernetesError struct { - Message string `json:"message"` - Code int `json:"code"` -} +// RetryingRoundTripper retries transient Kubernetes API server errors on a +// wrapped [http.RoundTripper]. The implementation lives in [kubeenv] so +// consumers can depend on that package without importing all of kube. +type RetryingRoundTripper = kubeenv.RetryingRoundTripper diff --git a/pkg/cli/roundtripper.go b/pkg/kubeenv/roundtripper.go similarity index 77% rename from pkg/cli/roundtripper.go rename to pkg/kubeenv/roundtripper.go index 040ecd48f..e00f93984 100644 --- a/pkg/cli/roundtripper.go +++ b/pkg/kubeenv/roundtripper.go @@ -14,7 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cli +// Package kubeenv holds small, cycle-free Kubernetes client helpers shared by +// higher-level packages (for example pkg/cli and pkg/kube). +package kubeenv import ( "bytes" @@ -24,21 +26,21 @@ import ( "strings" ) -// retryingRoundTripper retries selected transient Kubernetes API server failures. -// Keeping this in pkg/cli avoids importing pkg/kube from EnvSettings. -type retryingRoundTripper struct { - wrapped http.RoundTripper +// RetryingRoundTripper retries transient Kubernetes API server errors on a +// wrapped [http.RoundTripper]. +type RetryingRoundTripper struct { + Wrapped http.RoundTripper } -func (rt *retryingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { +func (rt *RetryingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { return rt.roundTrip(req, 1, nil) } -func (rt *retryingRoundTripper) roundTrip(req *http.Request, retry int, prevResp *http.Response) (*http.Response, error) { +func (rt *RetryingRoundTripper) roundTrip(req *http.Request, retry int, prevResp *http.Response) (*http.Response, error) { if retry < 0 { return prevResp, nil } - resp, rtErr := rt.wrapped.RoundTrip(req) + resp, rtErr := rt.Wrapped.RoundTrip(req) if rtErr != nil { return resp, rtErr } diff --git a/pkg/kube/roundtripper_test.go b/pkg/kubeenv/roundtripper_test.go similarity index 99% rename from pkg/kube/roundtripper_test.go rename to pkg/kubeenv/roundtripper_test.go index 96602c1f4..b921eac82 100644 --- a/pkg/kube/roundtripper_test.go +++ b/pkg/kubeenv/roundtripper_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package kube +package kubeenv import ( "encoding/json" From 8a954619255a82890a08b7d1fa9e86a437c4cebb Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Thu, 9 Apr 2026 13:54:24 -0700 Subject: [PATCH 262/571] chore: Update release notes script for Helm v4 Signed-off-by: George Jenkins --- scripts/release-notes.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh index 48328cb38..763c07c03 100755 --- a/scripts/release-notes.sh +++ b/scripts/release-notes.sh @@ -20,10 +20,10 @@ PREVIOUS_RELEASE=${PREVIOUS_RELEASE:-$1} ## Ensure Correct Usage if [[ -z "${PREVIOUS_RELEASE}" || -z "${RELEASE}" ]]; then echo Usage: - echo ./scripts/release-notes.sh v3.0.0 v3.1.0 + echo ./scripts/release-notes.sh v4.0.0 v4.1.0 echo or - echo PREVIOUS_RELEASE=v3.0.0 - echo RELEASE=v3.1.0 + echo PREVIOUS_RELEASE=v4.0.0 + echo RELEASE=v4.1.0 echo ./scripts/release-notes.sh exit 1 fi @@ -94,7 +94,7 @@ Download Helm ${RELEASE}. The common platform binaries are here: - [Windows amd64](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip) ([checksum](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip.sha256sum) / $(cat _dist/helm-${RELEASE}-windows-amd64.zip.sha256)) - [Windows arm64](https://get.helm.sh/helm-${RELEASE}-windows-arm64.zip) ([checksum](https://get.helm.sh/helm-${RELEASE}-windows-arm64.zip.sha256sum) / $(cat _dist/helm-${RELEASE}-windows-arm64.zip.sha256)) -The [Quickstart Guide](https://helm.sh/docs/intro/quickstart/) will get you going from there. For **upgrade instructions** or detailed installation notes, check the [install guide](https://helm.sh/docs/intro/install/). You can also use a [script to install](https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3) on any system with \`bash\`. +The [Quickstart Guide](https://helm.sh/docs/intro/quickstart/) will get you going from there. For **upgrade instructions** or detailed installation notes, check the [install guide](https://helm.sh/docs/intro/install/). You can also use a [script to install](https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4) on any system with \`bash\`. ## What's Next From d89e7c60762910204044c4215c7bb2f43ac3ef8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 21:34:08 +0000 Subject: [PATCH 263/571] chore(deps): bump golang.org/x/text from 0.35.0 to 0.36.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.35.0 to 0.36.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.35.0...v0.36.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.36.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 8afc66197..2344dbf5c 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.49.0 golang.org/x/term v0.41.0 - golang.org/x/text v0.35.0 + golang.org/x/text v0.36.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.35.3 k8s.io/apiextensions-apiserver v0.35.3 @@ -158,13 +158,13 @@ require ( go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/mod v0.33.0 // indirect + golang.org/x/mod v0.34.0 // indirect golang.org/x/net v0.52.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.42.0 // indirect + golang.org/x/tools v0.43.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/grpc v1.80.0 // indirect diff --git a/go.sum b/go.sum index f4a70b25a..3a2bf147b 100644 --- a/go.sum +++ b/go.sum @@ -391,8 +391,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -451,8 +451,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -461,8 +461,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= -golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= From 10fc5f335b5fbb09f5d04cb0450839790ae15634 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 21:43:41 +0000 Subject: [PATCH 264/571] chore(deps): bump golang.org/x/term from 0.41.0 to 0.42.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.41.0 to 0.42.0. - [Commits](https://github.com/golang/term/compare/v0.41.0...v0.42.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-version: 0.42.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 2344dbf5c..0683a8e24 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/tetratelabs/wazero v1.11.0 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.49.0 - golang.org/x/term v0.41.0 + golang.org/x/term v0.42.0 golang.org/x/text v0.36.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.35.3 @@ -162,7 +162,7 @@ require ( golang.org/x/net v0.52.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.42.0 // indirect + golang.org/x/sys v0.43.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.43.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect diff --git a/go.sum b/go.sum index 3a2bf147b..4d46d3ad1 100644 --- a/go.sum +++ b/go.sum @@ -433,8 +433,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -442,8 +442,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= From 953f5f031bb7fa8f3eccdea6520e09fd44fe3923 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 21:59:53 +0000 Subject: [PATCH 265/571] chore(deps): bump golang.org/x/crypto from 0.49.0 to 0.50.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.49.0 to 0.50.0. - [Commits](https://github.com/golang/crypto/compare/v0.49.0...v0.50.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.50.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0683a8e24..6fd3007dd 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/tetratelabs/wazero v1.11.0 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.49.0 + golang.org/x/crypto v0.50.0 golang.org/x/term v0.42.0 golang.org/x/text v0.36.0 gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 4d46d3ad1..1ff315d9a 100644 --- a/go.sum +++ b/go.sum @@ -385,8 +385,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= From c26be60d81e5cb6a147d6088477cf86fd5aaf1f0 Mon Sep 17 00:00:00 2001 From: Cairon Date: Fri, 10 Apr 2026 11:11:38 -0400 Subject: [PATCH 266/571] fix: add debug logging to HTTP getter for helm pull When running 'helm pull --debug', no debug output was printed because the HTTP getter did not emit any slog.Debug messages. This adds slog.Debug calls to log the URL being fetched and the response status when debug-level logging is enabled. Fixes helm/helm#31098 Signed-off-by: Cairon --- pkg/getter/httpgetter.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go index 2bc12bdbf..2eb2d5d8c 100644 --- a/pkg/getter/httpgetter.go +++ b/pkg/getter/httpgetter.go @@ -20,6 +20,7 @@ import ( "crypto/tls" "fmt" "io" + "log/slog" "net/http" "net/url" "sync" @@ -87,11 +88,13 @@ func (g *HTTPGetter) get(href string, opts getterOptions) (*bytes.Buffer, error) return nil, err } + slog.Debug("fetching", "url", href) resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() + slog.Debug("fetch complete", "url", href, "status", resp.Status, "content-length", resp.ContentLength) if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to fetch %s : %s", href, resp.Status) } From a27f1add79c6c02459413dbb60f8438d8051cf06 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Fri, 10 Apr 2026 18:44:27 +0100 Subject: [PATCH 267/571] fix(templating): fix wrong YAML separator parsing for post-renderers (#31941) Signed-off-by: Matheus Pimenta --- pkg/action/action.go | 72 ++---- pkg/action/action_test.go | 423 +++++++++++++++++++++++++------- pkg/release/v1/util/manifest.go | 9 + 3 files changed, 367 insertions(+), 137 deletions(-) diff --git a/pkg/action/action.go b/pkg/action/action.go index 75b6cf8a0..c93950103 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -27,6 +27,7 @@ import ( "path" "path/filepath" "slices" + "sort" "strings" "sync" "text/template" @@ -144,39 +145,6 @@ const ( filenameAnnotation = "postrenderer.helm.sh/postrender-filename" ) -// fixDocSeparators ensures YAML document separators ("---") are always -// followed by a newline in rendered template content. Go template whitespace -// trimming ({{-) can remove the newline after "---", producing e.g. -// "---apiVersion: v1" which is not a valid YAML document separator. -// This function inserts a newline after any "---" at the start of a line -// that is immediately followed by non-whitespace content. -func fixDocSeparators(content string) string { - var b strings.Builder - remaining := content - for { - // Find "---" at the start of a line (or start of content). - idx := strings.Index(remaining, "---") - if idx == -1 { - b.WriteString(remaining) - break - } - // "---" must be at the start of a line: either idx==0 or preceded by '\n'. - if idx > 0 && remaining[idx-1] != '\n' { - b.WriteString(remaining[:idx+3]) - remaining = remaining[idx+3:] - continue - } - b.WriteString(remaining[:idx+3]) - remaining = remaining[idx+3:] - // If "---" is followed by non-whitespace (e.g. "---apiVersion"), - // insert a newline to make it a proper document separator. - if len(remaining) > 0 && remaining[0] != '\n' && remaining[0] != '\r' && remaining[0] != ' ' && remaining[0] != '\t' { - b.WriteByte('\n') - } - } - return b.String() -} - // annotateAndMerge combines multiple YAML files into a single stream of documents, // adding filename annotations to each document for later reconstruction. func annotateAndMerge(files map[string]string) (string, error) { @@ -192,22 +160,32 @@ func annotateAndMerge(files map[string]string) (string, error) { continue } - // Fix document separators where Go template whitespace trimming - // ({{-) has removed the newline after "---", producing e.g. - // "---apiVersion: v1" which is not a valid YAML document - // separator. Insert the missing newline so kio.ParseAll can - // parse the content correctly. - content = fixDocSeparators(content) - - manifests, err := kio.ParseAll(content) - if err != nil { - return "", fmt.Errorf("parsing %s: %w", fname, err) + // For consistency with the non-post-renderers code path, we need + // to use releaseutil.SplitManifests here to split the file into + // individual documents before feeding them to kio.ParseAll. In + // Chart API before v3 this function had leniency for badly-written + // Go templates, so this must be preserved for older charts. + splitDocs := releaseutil.SplitManifests(content) + keys := make([]string, 0, len(splitDocs)) + for k := range splitDocs { + keys = append(keys, k) } - for _, manifest := range manifests { - if err := manifest.PipeE(kyaml.SetAnnotation(filenameAnnotation, fname)); err != nil { - return "", fmt.Errorf("annotating %s: %w", fname, err) + sort.Sort(releaseutil.BySplitManifestsOrder(keys)) + for _, key := range keys { + doc := splitDocs[key] + if strings.TrimSpace(doc) == "" { + continue + } + manifests, err := kio.ParseAll(doc) + if err != nil { + return "", fmt.Errorf("parsing %s: %w", fname, err) + } + for _, manifest := range manifests { + if err := manifest.PipeE(kyaml.SetAnnotation(filenameAnnotation, fname)); err != nil { + return "", fmt.Errorf("annotating %s: %w", fname, err) + } + combinedManifests = append(combinedManifests, manifest) } - combinedManifests = append(combinedManifests, manifest) } } diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index 62e8adfea..a2b170206 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -403,96 +403,6 @@ func (m *mockPostRenderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, return bytes.NewBufferString(content), nil } -func TestFixDocSeparators(t *testing.T) { - tests := []struct { - name string - input string - expected string - }{ - { - name: "no separator", - input: "apiVersion: v1\nkind: Service\n", - expected: "apiVersion: v1\nkind: Service\n", - }, - { - name: "separator on its own line", - input: "---\napiVersion: v1\nkind: Service\n", - expected: "---\napiVersion: v1\nkind: Service\n", - }, - { - name: "leading separator glued to content", - input: "---apiVersion: v1\nkind: Service\n", - expected: "---\napiVersion: v1\nkind: Service\n", - }, - { - name: "mid-content separator glued to content", - input: "apiVersion: v1\nkind: ConfigMap\n---apiVersion: v1\nkind: Service\n", - expected: "apiVersion: v1\nkind: ConfigMap\n---\napiVersion: v1\nkind: Service\n", - }, - { - name: "multiple separators all proper", - input: "---\napiVersion: v1\n---\napiVersion: v1\n", - expected: "---\napiVersion: v1\n---\napiVersion: v1\n", - }, - { - name: "multiple separators some glued", - input: "---apiVersion: v1\nkind: ConfigMap\n---apiVersion: v1\nkind: Service\n", - expected: "---\napiVersion: v1\nkind: ConfigMap\n---\napiVersion: v1\nkind: Service\n", - }, - { - name: "empty string", - input: "", - expected: "", - }, - { - name: "only separator", - input: "---\n", - expected: "---\n", - }, - { - name: "triple dash in a value is not a separator", - input: "data:\n key: ---value\n", - expected: "data:\n key: ---value\n", - }, - { - name: "realistic multi-doc template output", - input: "apiVersion: v1\nkind: Deployment\n---\napiVersion: v1\nkind: Ingress\n---apiVersion: v1\nkind: Service\n", - expected: "apiVersion: v1\nkind: Deployment\n---\napiVersion: v1\nkind: Ingress\n---\napiVersion: v1\nkind: Service\n", - }, - { - name: "separator followed by carriage return", - input: "---\r\napiVersion: v1\n", - expected: "---\r\napiVersion: v1\n", - }, - { - name: "separator followed by space", - input: "--- \napiVersion: v1\n", - expected: "--- \napiVersion: v1\n", - }, - { - name: "separator followed by tab", - input: "---\t\napiVersion: v1\n", - expected: "---\t\napiVersion: v1\n", - }, - { - name: "four dashes on its own line", - input: "----\napiVersion: v1\n", - expected: "---\n-\napiVersion: v1\n", - }, - { - name: "four dashes followed by text", - input: "----more\napiVersion: v1\n", - expected: "---\n-more\napiVersion: v1\n", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.expected, fixDocSeparators(tt.input)) - }) - } -} - func TestAnnotateAndMerge(t *testing.T) { tests := []struct { name string @@ -690,6 +600,339 @@ metadata: name: test-svc annotations: postrenderer.helm.sh/postrender-filename: 'templates/all.yaml' +`, + }, + { + name: "ConfigMap with embedded CA certificate", + files: map[string]string{ + "templates/configmap.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: ca-bundle +data: + ca.crt: | + ------BEGIN CERTIFICATE------ + MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zAKBggqhkjOPQQDAzASMRAw + DgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYwMDAw + WjASMRAwDgYDVQQKEwdBY21lIENvMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7Rmm + ------END CERTIFICATE------ + ------BEGIN CERTIFICATE------ + MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zAKBggqhkjOPQQDAzASMRAw + DgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYwMDAw + WjASMRAwDgYDVQQKEwdBY21lIENvMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7Rmm + ------END CERTIFICATE------ +`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: ca-bundle + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/configmap.yaml' +data: + ca.crt: |- + ------BEGIN CERTIFICATE------ + MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zAKBggqhkjOPQQDAzASMRAw + DgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYwMDAw + WjASMRAwDgYDVQQKEwdBY21lIENvMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7Rmm + ------END CERTIFICATE------ + ------BEGIN CERTIFICATE------ + MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zAKBggqhkjOPQQDAzASMRAw + DgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYwMDAw + WjASMRAwDgYDVQQKEwdBY21lIENvMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7Rmm + ------END CERTIFICATE------ +`, + }, + { + name: "consecutive dashes in YAML value are not treated as document separators", + files: map[string]string{ + "templates/configmap.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-cm +data: + config: | + # --------------------------------------------------------------------------- + [section] + key = value + # --------------------------------------------------------------------------- +`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test-cm + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/configmap.yaml' +data: + config: |- + # --------------------------------------------------------------------------- + [section] + key = value + # --------------------------------------------------------------------------- +`, + }, + { + name: "JSON with dashes in values is not corrupted", + files: map[string]string{ + "templates/dashboard.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: dashboard +data: + dashboard.json: | + {"options":{"---------":{"color":"#292929","text":"N/A"}}} +`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: dashboard + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/dashboard.yaml' +data: + dashboard.json: |- + {"options":{"---------":{"color":"#292929","text":"N/A"}}} +`, + }, + + // **Note for Chart API v3**: This input should return an _ERROR_ in Chart API v3. + // See the comment on the releaseutil.SplitManifests function for more details. + { + name: "multiple glued separators in same file", + files: map[string]string{ + "templates/multi.yaml": ` +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm3 +`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/multi.yaml' +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/multi.yaml' +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm3 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/multi.yaml' +`, + }, + + // **Note for Chart API v3**: This input should return an _ERROR_ in Chart API v3. + // See the comment on the releaseutil.SplitManifests function for more details. + { + name: "mixed glued and proper separators", + files: map[string]string{ + "templates/mixed.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm3 +`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/mixed.yaml' +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/mixed.yaml' +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm3 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/mixed.yaml' +`, + }, + { + name: "12 documents preserve in-file order", + files: map[string]string{ + "templates/many.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-01 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-02 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-03 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-04 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-05 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-06 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-07 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-08 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-09 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-10 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-11 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-12 +`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-01 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/many.yaml' +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-02 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/many.yaml' +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-03 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/many.yaml' +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-04 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/many.yaml' +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-05 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/many.yaml' +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-06 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/many.yaml' +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-07 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/many.yaml' +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-08 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/many.yaml' +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-09 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/many.yaml' +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-10 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/many.yaml' +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-11 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/many.yaml' +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm-12 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/many.yaml' `, }, } diff --git a/pkg/release/v1/util/manifest.go b/pkg/release/v1/util/manifest.go index 9a87949f8..3160599bc 100644 --- a/pkg/release/v1/util/manifest.go +++ b/pkg/release/v1/util/manifest.go @@ -36,6 +36,15 @@ type SimpleHead struct { var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") // SplitManifests takes a string of manifest and returns a map contains individual manifests +// +// **Note for Chart API v3**: This function (due to the regex above) has allowed _WRONG_ +// Go templates to be defined inside charts across the years. The generated text from Go +// templates may contain `---apiVersion: v1`, and this function magically splits this back +// to `---\napiVersion: v1`. This has caused issues recently after Helm 4 introduced +// kio.ParseAll to inject annotations when post-renderers are used. In Chart API v3, +// we should kill this regex with fire (or change it) and expose charts doing the wrong +// thing Go template-wise. Helm should say a big _NO_ to charts doing the wrong thing, +// with or without post-renderers. func SplitManifests(bigFile string) map[string]string { // Basically, we're quickly splitting a stream of YAML documents into an // array of YAML docs. The file name is just a place holder, but should be From b5c7c80de317643e383ca2926ebc0ad884021bba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 21:33:04 +0000 Subject: [PATCH 268/571] chore(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 7.0.0 to 7.0.1. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/bbbca2ddaa5d8feaa63e36b76fdaad77386f024f...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 0a84609ab..75406c17a 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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: SARIF file path: results.sarif From 722dd3e3a82b06fbda60ebf24edc653e7b74e3f9 Mon Sep 17 00:00:00 2001 From: Jorge Rocamora <33847633+aeroyorch@users.noreply.github.com> Date: Sat, 11 Apr 2026 21:03:12 +0200 Subject: [PATCH 269/571] Add duration functions Signed-off-by: Jorge Rocamora <33847633+aeroyorch@users.noreply.github.com> --- pkg/engine/funcs.go | 226 +++++++++++++++++++++++++++++++++++ pkg/engine/funcs_test.go | 247 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 473 insertions(+) diff --git a/pkg/engine/funcs.go b/pkg/engine/funcs.go index e03c13b38..431f82f63 100644 --- a/pkg/engine/funcs.go +++ b/pkg/engine/funcs.go @@ -19,9 +19,15 @@ package engine import ( "bytes" "encoding/json" + "errors" + "fmt" "maps" + "math" + "reflect" + "strconv" "strings" "text/template" + "time" "github.com/BurntSushi/toml" "github.com/Masterminds/sprig/v3" @@ -62,6 +68,19 @@ func funcMap() template.FuncMap { "fromJson": fromJSON, "fromJsonArray": fromJSONArray, + // Duration helpers + "mustToDuration": mustToDuration, + "durationSeconds": durationSeconds, + "durationMilliseconds": durationMilliseconds, + "durationMicroseconds": durationMicroseconds, + "durationNanoseconds": durationNanoseconds, + "durationMinutes": durationMinutes, + "durationHours": durationHours, + "durationDays": durationDays, + "durationWeeks": durationWeeks, + "durationRoundTo": durationRoundTo, + "durationTruncateTo": durationTruncateTo, + // This is a placeholder for the "include" function, which is // late-bound to a template. By declaring it here, we preserve the // integrity of the linter. @@ -249,3 +268,210 @@ func fromJSONArray(str string) []any { } return a } + +// ----------------------------------------------------------------------------- +// Duration helpers (numeric and time.Duration returns) +// ----------------------------------------------------------------------------- + +const ( + maxDurationSeconds = int64(math.MaxInt64 / int64(time.Second)) + minDurationSeconds = int64(math.MinInt64 / int64(time.Second)) + maxDurationSecondsFloat = float64(math.MaxInt64) / float64(time.Second) + minDurationSecondsFloat = float64(math.MinInt64) / float64(time.Second) +) + +func durationFromSecondsInt(seconds int64) (time.Duration, error) { + if seconds > maxDurationSeconds || seconds < minDurationSeconds { + return 0, fmt.Errorf("duration seconds overflow: %d", seconds) + } + return time.Duration(seconds) * time.Second, nil +} + +func durationFromSecondsUint(seconds uint64) (time.Duration, error) { + if seconds > uint64(maxDurationSeconds) { + return 0, fmt.Errorf("duration seconds overflow: %d", seconds) + } + return time.Duration(int64(seconds)) * time.Second, nil +} + +func durationFromSecondsFloat(seconds float64) (time.Duration, error) { + if math.IsNaN(seconds) || math.IsInf(seconds, 0) { + return 0, fmt.Errorf("invalid duration seconds: %v", seconds) + } + if seconds > maxDurationSecondsFloat || seconds < minDurationSecondsFloat { + return 0, fmt.Errorf("duration seconds overflow: %v", seconds) + } + nanos := seconds * float64(time.Second) + if nanos > float64(math.MaxInt64) || nanos < float64(math.MinInt64) { + return 0, fmt.Errorf("duration nanoseconds overflow: %v", nanos) + } + return time.Duration(nanos), nil +} + +// asDuration converts common template values into a time.Duration. +// +// Supported inputs: +// - time.Duration +// - string duration values parsed by time.ParseDuration (e.g. "1h2m3s") +// - numeric strings treated as seconds (e.g. "2.5") +// - ints and uints treated as seconds +// - floats treated as seconds +func asDuration(v any) (time.Duration, error) { + switch x := v.(type) { + case time.Duration: + return x, nil + + case string: + s := strings.TrimSpace(x) + if s == "" { + return 0, errors.New("empty duration") + } + if d, err := time.ParseDuration(s); err == nil { + return d, nil + } + if f, err := strconv.ParseFloat(s, 64); err == nil { + return durationFromSecondsFloat(f) + } + return 0, fmt.Errorf("could not parse duration %q", x) + + case nil: + return 0, errors.New("invalid duration") + } + + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return durationFromSecondsInt(rv.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return durationFromSecondsUint(rv.Uint()) + case reflect.Float32, reflect.Float64: + return durationFromSecondsFloat(rv.Float()) + default: + return 0, fmt.Errorf("unsupported duration type %T", v) + } +} + +// mustToDuration takes anything and attempts to parse as a duration returning a time.Duration. +// +// This is designed to be called from a template when need to ensure that a +// duration is valid. +func mustToDuration(v any) time.Duration { + d, err := asDuration(v) + if err != nil { + panic(err) + } + return d +} + +// durationSeconds converts a duration to seconds (float64). +// On error it returns 0. +func durationSeconds(v any) float64 { + d, err := asDuration(v) + if err != nil { + return 0 + } + return d.Seconds() +} + +// durationMilliseconds converts a duration to milliseconds (int64). +// On error it returns 0. +func durationMilliseconds(v any) int64 { + d, err := asDuration(v) + if err != nil { + return 0 + } + return d.Milliseconds() +} + +// durationMicroseconds converts a duration to microseconds (int64). +// On error it returns 0. +func durationMicroseconds(v any) int64 { + d, err := asDuration(v) + if err != nil { + return 0 + } + return d.Microseconds() +} + +// durationNanoseconds converts a duration to nanoseconds (int64). +// On error it returns 0. +func durationNanoseconds(v any) int64 { + d, err := asDuration(v) + if err != nil { + return 0 + } + return d.Nanoseconds() +} + +// durationMinutes converts a duration to minutes (float64). +// On error it returns 0. +func durationMinutes(v any) float64 { + d, err := asDuration(v) + if err != nil { + return 0 + } + return d.Minutes() +} + +// durationHours converts a duration to hours (float64). +// On error it returns 0. +func durationHours(v any) float64 { + d, err := asDuration(v) + if err != nil { + return 0 + } + return d.Hours() +} + +// durationDays converts a duration to days (float64). (Not in Go's stdlib; handy in templates.) +// On error it returns 0. +func durationDays(v any) float64 { + d, err := asDuration(v) + if err != nil { + return 0 + } + return d.Hours() / 24.0 +} + +// durationWeeks converts a duration to weeks (float64). (Not in Go's stdlib; handy in templates.) +// On error it returns 0. +func durationWeeks(v any) float64 { + d, err := asDuration(v) + if err != nil { + return 0 + } + return d.Hours() / 24.0 / 7.0 +} + +// durationRoundTo rounds v to the nearest multiple of m. +// Returns a time.Duration. +// +// v and m accept the same forms as asDuration (e.g. "2h13m", "30s"). +// On error, it returns time.Duration(0). If m is invalid, it returns v. +func durationRoundTo(v any, m any) time.Duration { + d, err := asDuration(v) + if err != nil { + return 0 + } + mul, err := asDuration(m) + if err != nil { + return d + } + return d.Round(mul) +} + +// durationTruncateTo truncates v toward zero to a multiple of m. +// Returns a time.Duration. +// +// On error, it returns time.Duration(0). If m is invalid, it returns v. +func durationTruncateTo(v any, m any) time.Duration { + d, err := asDuration(v) + if err != nil { + return 0 + } + mul, err := asDuration(m) + if err != nil { + return d + } + return d.Truncate(mul) +} diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go index be9d0153f..cf6a8d5c9 100644 --- a/pkg/engine/funcs_test.go +++ b/pkg/engine/funcs_test.go @@ -17,11 +17,14 @@ limitations under the License. package engine import ( + "math" "strings" "testing" "text/template" + "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestFuncs(t *testing.T) { @@ -151,6 +154,17 @@ keyInElement1 = "valueInElement1"`, }, { tpl: `{{ mustToJson . }}`, vars: loopMap, + }, { + tpl: `{{ mustToDuration 30 }}`, + expect: `30s`, + vars: nil, + }, { + tpl: `{{ mustToDuration "1m30s" }}`, + expect: `1m30s`, + vars: nil, + }, { + tpl: `{{ mustToDuration "foo" }}`, + vars: nil, }, { tpl: `{{ toYaml . }}`, expect: "", // should return empty string and swallow error @@ -181,6 +195,239 @@ keyInElement1 = "valueInElement1"`, } } +func TestDurationHelpers(t *testing.T) { + tests := []struct { + name string + tpl string + vars any + expect string + }{{ + name: "durationSeconds parses duration string", + tpl: `{{ durationSeconds "1m30s" }}`, + expect: `90`, + }, { + name: "durationSeconds parses numeric string as seconds", + tpl: `{{ durationSeconds "2.5" }}`, + expect: `2.5`, + }, { + name: "durationSeconds trims whitespace around numeric string", + tpl: `{{ durationSeconds " 2.5 " }}`, + expect: `2.5`, + }, { + name: "durationSeconds int treated as seconds", + tpl: `{{ durationSeconds 2 }}`, + expect: `2`, + }, { + name: "durationSeconds float treated as seconds", + tpl: `{{ durationSeconds 2.5 }}`, + expect: `2.5`, + }, { + name: "durationSeconds uint treated as seconds", + tpl: `{{ durationSeconds . }}`, + vars: uint(2), + expect: `2`, + }, { + name: "durationSeconds time.Duration passthrough", + tpl: `{{ durationSeconds . }}`, + vars: 1500 * time.Millisecond, + expect: `1.5`, + }, { + name: "invalid duration string returns 0", + tpl: `{{ durationSeconds "nope" }}`, + expect: `0`, + }, { + name: "empty duration string returns 0", + tpl: `{{ durationSeconds "" }}`, + expect: `0`, + }, { + name: "whitespace-only duration string returns 0", + tpl: `{{ durationSeconds " " }}`, + expect: `0`, + }, { + name: "nil returns 0", + tpl: `{{ durationSeconds . }}`, + vars: nil, + expect: `0`, + }, { + name: "durationSeconds uint overflow returns 0", + tpl: `{{ durationSeconds . }}`, + vars: uint64(math.MaxInt64) + 1, + expect: `0`, + }, { + name: "durationSeconds int overflow returns 0", + tpl: `{{ durationSeconds . }}`, + vars: maxDurationSeconds + 1, + expect: `0`, + }, { + name: "durationSeconds int underflow returns 0", + tpl: `{{ durationSeconds . }}`, + vars: minDurationSeconds - 1, + expect: `0`, + }, { + name: "durationSeconds float overflow returns 0", + tpl: `{{ durationSeconds . }}`, + vars: maxDurationSecondsFloat + 0.5, + expect: `0`, + }, { + name: "durationSeconds float underflow returns 0", + tpl: `{{ durationSeconds . }}`, + vars: minDurationSecondsFloat - 0.5, + expect: `0`, + }, { + name: "durationSeconds NaN returns 0", + tpl: `{{ durationSeconds . }}`, + vars: math.NaN(), + expect: `0`, + }, { + name: "durationSeconds Inf returns 0", + tpl: `{{ durationSeconds . }}`, + vars: math.Inf(1), + expect: `0`, + }, { + name: "durationMilliseconds int seconds", + tpl: `{{ durationMilliseconds 2 }}`, + expect: `2000`, + }, { + name: "durationMilliseconds float seconds", + tpl: `{{ durationMilliseconds 1.5 }}`, + expect: `1500`, + }, { + name: "durationMicroseconds int seconds", + tpl: `{{ durationMicroseconds 2 }}`, + expect: `2000000`, + }, { + name: "durationNanoseconds int seconds", + tpl: `{{ durationNanoseconds 2 }}`, + expect: `2000000000`, + }, { + name: "durationMinutes parses duration string", + tpl: `{{ durationMinutes "90s" }}`, + expect: `1.5`, + }, { + name: "durationHours parses duration string", + tpl: `{{ durationHours "90m" }}`, + expect: `1.5`, + }, { + name: "durationDays parses duration string", + tpl: `{{ durationDays "36h" }}`, + expect: `1.5`, + }, { + name: "durationDays numeric seconds", + tpl: `{{ durationDays 86400 }}`, + expect: `1`, + }, { + name: "durationWeeks parses duration string", + tpl: `{{ durationWeeks "168h" }}`, + expect: `1`, + }, { + name: "durationWeeks parses fractional weeks", + tpl: `{{ durationWeeks "252h" }}`, + expect: `1.5`, + }, { + name: "durationRoundTo numeric seconds", + tpl: `{{ durationRoundTo 93 60 }}`, // 93s rounded to 60s = 120s + expect: `2m0s`, + }, { + name: "durationTruncateTo numeric seconds", + tpl: `{{ durationTruncateTo 93 60 }}`, // 93s truncated to 60s = 60s + expect: `1m0s`, + }, { + name: "durationRoundTo accepts duration-string multiplier", + tpl: `{{ durationRoundTo "93s" "1m" }}`, + expect: `2m0s`, + }, { + name: "durationTruncateTo accepts duration-string multiplier", + tpl: `{{ durationTruncateTo "93s" "1m" }}`, + expect: `1m0s`, + }, { + name: "durationRoundTo invalid m returns v unchanged", + tpl: `{{ durationRoundTo "93s" "nope" }}`, + expect: `1m33s`, + }, { + name: "durationTruncateTo invalid m returns v unchanged", + tpl: `{{ durationTruncateTo "93s" "nope" }}`, + expect: `1m33s`, + }, { + name: "durationRoundTo zero m returns v unchanged", + tpl: `{{ durationRoundTo "93s" 0 }}`, + expect: `1m33s`, + }, { + name: "durationTruncateTo negative m returns v unchanged", + tpl: `{{ durationTruncateTo "93s" -1 }}`, + expect: `1m33s`, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var b strings.Builder + err := template.Must(template.New("test").Funcs(funcMap()).Parse(tt.tpl)).Execute(&b, tt.vars) + require.NoError(t, err, tt.tpl) + assert.Equal(t, tt.expect, b.String(), tt.tpl) + }) + } + + mustErrTests := []struct { + name string + tpl string + vars any + }{{ + name: "mustToDuration invalid string", + tpl: `{{ mustToDuration "nope" }}`, + }, { + name: "mustToDuration empty string", + tpl: `{{ mustToDuration "" }}`, + }, { + name: "mustToDuration whitespace string", + tpl: `{{ mustToDuration " " }}`, + }, { + name: "mustToDuration unsupported type", + tpl: `{{ mustToDuration . }}`, + vars: []int{1, 2, 3}, + }, { + name: "mustToDuration uint overflow", + tpl: `{{ mustToDuration . }}`, + vars: uint64(math.MaxInt64) + 1, + }, { + name: "mustToDuration int overflow", + tpl: `{{ mustToDuration . }}`, + vars: maxDurationSeconds + 1, + }, { + name: "mustToDuration int underflow", + tpl: `{{ mustToDuration . }}`, + vars: minDurationSeconds - 1, + }, { + name: "mustToDuration float overflow", + tpl: `{{ mustToDuration . }}`, + vars: maxDurationSecondsFloat + 0.5, + }, { + name: "mustToDuration float underflow", + tpl: `{{ mustToDuration . }}`, + vars: minDurationSecondsFloat - 0.5, + }, { + name: "mustToDuration NaN", + tpl: `{{ mustToDuration . }}`, + vars: math.NaN(), + }, { + name: "mustToDuration Inf", + tpl: `{{ mustToDuration . }}`, + vars: math.Inf(-1), + }, + } + + for _, tt := range mustErrTests { + t.Run(tt.name, func(t *testing.T) { + var b strings.Builder + tmpl := template.Must( + template.New("test"). + Funcs(funcMap()). + Parse(tt.tpl), + ) + err := tmpl.Execute(&b, tt.vars) + require.Error(t, err, tt.tpl) + }) + } +} + // This test to check a function provided by sprig is due to a change in a // dependency of sprig. mergo in v0.3.9 changed the way it merges and only does // public fields (i.e. those starting with a capital letter). This test, from From 5cb4e7d992d85d372f5d86c238330102d936bfe5 Mon Sep 17 00:00:00 2001 From: Johannes Lohmer Date: Sat, 28 Mar 2026 10:23:17 +0000 Subject: [PATCH 270/571] test(values): add tests for subchart nil value regressions Three test cases that cover the regression scenarios introduced by the #31644 nil preservation fix: - subchart default nils should be cleaned up when parent doesn't set those keys (#31919) - user-supplied null should erase subchart defaults (#31919) - subchart default nil should not shadow global values via pluck (#31971) Tests are expected to fail until the regression is fixed. Signed-off-by: Johannes Lohmer --- .gitignore | 1 + pkg/chart/common/util/coalesce_test.go | 118 +++++++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/.gitignore b/.gitignore index 0fd2c6bda..2209e9809 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .DS_Store .coverage/ .idea +.claude .vimrc .vscode/ .devcontainer/ diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index 1d0baa84d..f7bf541a2 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -765,3 +765,121 @@ func TestCoalesceValuesEmptyMapWithNils(t *testing.T) { is.True(ok, "Expected data.baz key to be present but it was removed") is.Nil(data["baz"], "Expected data.baz key to be nil but it is not") } + +// TestCoalesceValuesSubchartDefaultNilsCleaned tests that nil values in subchart defaults +// are cleaned up during coalescing when the parent doesn't set those keys. +// Regression test for issue #31919. +func TestCoalesceValuesSubchartDefaultNilsCleaned(t *testing.T) { + is := assert.New(t) + + // Subchart has a default with nil values (e.g. keyMapping: {password: null}) + subchart := &chart.Chart{ + Metadata: &chart.Metadata{Name: "child"}, + Values: map[string]any{ + "keyMapping": map[string]any{ + "password": nil, + }, + }, + } + + parent := withDeps(&chart.Chart{ + Metadata: &chart.Metadata{Name: "parent"}, + Values: map[string]any{}, + }, subchart) + + // Parent user values don't mention keyMapping at all + vals := map[string]any{} + + v, err := CoalesceValues(parent, vals) + is.NoError(err) + + childVals, ok := v["child"].(map[string]any) + is.True(ok, "child values should be a map") + + keyMapping, ok := childVals["keyMapping"].(map[string]any) + is.True(ok, "keyMapping should be a map") + + // The nil "password" key from chart defaults should be cleaned up + _, ok = keyMapping["password"] + is.False(ok, "Expected keyMapping.password (nil from chart defaults) to be removed, but it is still present") +} + +// TestCoalesceValuesUserNullErasesSubchartDefault tests that a user-supplied null +// value erases a subchart's default value during coalescing. +// Regression test for issue #31919. +func TestCoalesceValuesUserNullErasesSubchartDefault(t *testing.T) { + is := assert.New(t) + + subchart := &chart.Chart{ + Metadata: &chart.Metadata{Name: "child"}, + Values: map[string]any{ + "someKey": "default", + }, + } + + parent := withDeps(&chart.Chart{ + Metadata: &chart.Metadata{Name: "parent"}, + Values: map[string]any{}, + }, subchart) + + // User explicitly nullifies the subchart key via parent values + vals := map[string]any{ + "child": map[string]any{ + "someKey": nil, + }, + } + + v, err := CoalesceValues(parent, vals) + is.NoError(err) + + childVals, ok := v["child"].(map[string]any) + is.True(ok, "child values should be a map") + + // someKey should be erased — user null overrides subchart default + _, ok = childVals["someKey"] + is.False(ok, "Expected someKey to be removed by user null override, but it is still present") +} + +// TestCoalesceValuesSubchartNilDoesNotShadowGlobal tests that a nil value in +// subchart defaults doesn't shadow a global value accessible via pluck-like access. +// Regression test for issue #31971. +func TestCoalesceValuesSubchartNilDoesNotShadowGlobal(t *testing.T) { + is := assert.New(t) + + subchart := &chart.Chart{ + Metadata: &chart.Metadata{Name: "child"}, + Values: map[string]any{ + "ingress": map[string]any{ + "feature": nil, // nil in subchart defaults + }, + }, + } + + parent := withDeps(&chart.Chart{ + Metadata: &chart.Metadata{Name: "parent"}, + Values: map[string]any{}, + }, subchart) + + // Parent sets the global value + vals := map[string]any{ + "global": map[string]any{ + "ingress": map[string]any{ + "feature": true, + }, + }, + } + + v, err := CoalesceValues(parent, vals) + is.NoError(err) + + childVals, ok := v["child"].(map[string]any) + is.True(ok, "child values should be a map") + + ingress, ok := childVals["ingress"].(map[string]any) + is.True(ok, "ingress should be a map") + + // The nil "feature" from subchart defaults should be cleaned up, + // so that pluck can fall through to the global value + _, ok = ingress["feature"] + is.False(ok, "Expected ingress.feature (nil from chart defaults) to be removed so global can be used via pluck, but it is still present") +} From 6eb4ebf0e1afb0c63d748bf116145a5b9e0842b7 Mon Sep 17 00:00:00 2001 From: Johannes Lohmer Date: Sat, 28 Mar 2026 10:23:21 +0000 Subject: [PATCH 271/571] test(values): add test for subchart nil producing %!s() Regression test for the Bitnami common.secrets.key issue. Signed-off-by: Johannes Lohmer --- pkg/engine/engine_test.go | 62 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index c674a11ec..869b5d202 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -1505,3 +1505,65 @@ func TestTraceableError_NoTemplateForm(t *testing.T) { } } } + +// TestRenderSubchartDefaultNilNoStringify tests the full pipeline: subchart default +// nil values should not produce "%!s()" in rendered template output. +// Regression test for the Bitnami common.secrets.key issue. +func TestRenderSubchartDefaultNilNoStringify(t *testing.T) { + modTime := time.Now() + + // Subchart has a default with nil values + subchart := &chart.Chart{ + Metadata: &chart.Metadata{Name: "child"}, + Templates: []*common.File{ + { + Name: "templates/test.yaml", + ModTime: modTime, + Data: []byte(`{{- if hasKey .Values.keyMapping "password" -}}{{- printf "subPath: %s" (index .Values.keyMapping "password") -}}{{- else -}}subPath: fallback{{- end -}}`), + }, + }, + Values: map[string]any{ + "keyMapping": map[string]any{ + "password": nil, // nil in chart defaults + }, + }, + } + + parent := &chart.Chart{ + Metadata: &chart.Metadata{Name: "parent"}, + Values: map[string]any{}, + } + parent.AddDependency(subchart) + + // Parent user values don't set keyMapping + injValues := map[string]any{} + + tmp, err := util.CoalesceValues(parent, injValues) + if err != nil { + t.Fatalf("Failed to coalesce values: %s", err) + } + + inject := common.Values{ + "Values": tmp, + "Chart": parent.Metadata, + "Release": common.Values{ + "Name": "test-release", + }, + } + + out, err := Render(parent, inject) + if err != nil { + t.Fatalf("Failed to render templates: %s", err) + } + + rendered := out["parent/charts/child/templates/test.yaml"] + + if strings.Contains(rendered, "%!s()") { + t.Errorf("Rendered output contains %%!s(), got: %q", rendered) + } + + expected := "subPath: fallback" + if rendered != expected { + t.Errorf("Expected %q, got %q", expected, rendered) + } +} From 00638773d1366dc962c785de3d297cf0279b9a0d Mon Sep 17 00:00:00 2001 From: Johannes Lohmer Date: Sat, 28 Mar 2026 20:59:31 +0100 Subject: [PATCH 272/571] fix(values): do not copy chart-default nils into coalesced values Only user-supplied nils should survive coalescing. Chart-default nils defaults, not just user overrides. This caused: - %!s() in templates using Bitnami common.secrets.key (#31919) - pluck fallbacks returning nil instead of falling through to globals (#31971) Fixes #31919 Fixes #31971 Signed-off-by: Johannes Lohmer --- pkg/chart/common/util/coalesce.go | 29 +++++++++++++++++++++++++- pkg/cmd/testdata/output/issue-9027.txt | 10 +++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/pkg/chart/common/util/coalesce.go b/pkg/chart/common/util/coalesce.go index 5994febbc..90e6640e9 100644 --- a/pkg/chart/common/util/coalesce.go +++ b/pkg/chart/common/util/coalesce.go @@ -251,6 +251,12 @@ func coalesceValues(printf printFn, c chart.Charter, v map[string]any, prefix st // If the key is a child chart, coalesce tables with Merge set to true merge := childChartMergeTrue(c, key, merge) + // When coalescing, clean nils from chart defaults before merging + // so they don't leak into the result. + if !merge { + cleanNilValues(src) + } + // Because v has higher precedence than nv, dest values override src // values. coalesceTablesFullKey(printf, dest, src, concatPrefix(subPrefix, key), merge) @@ -258,6 +264,16 @@ func coalesceValues(printf printFn, c chart.Charter, v map[string]any, prefix st } } else { // If the key is not in v, copy it from nv. + // When coalescing, skip chart default nils and clean nils from + // nested maps so they don't shadow globals or produce %!s(). + if !merge { + if val == nil { + continue + } + if sub, ok := val.(map[string]any); ok { + cleanNilValues(sub) + } + } v[key] = val } } @@ -326,7 +342,6 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]any, prefix strin // But if src also has nil (or key not in src), preserve the nil delete(dst, key) } else if !ok { - // key not in user values, preserve src value (including nil) dst[key] = val } else if istable(val) { if istable(dv) { @@ -341,6 +356,18 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]any, prefix strin return dst } +// cleanNilValues recursively removes nil entries from a map so that chart +// default nils don't leak into the coalesced result. +func cleanNilValues(m map[string]any) { + for key, val := range m { + if val == nil { + delete(m, key) + } else if sub, ok := val.(map[string]any); ok { + cleanNilValues(sub) + } + } +} + // istable is a special-purpose function to see if the present thing matches the definition of a YAML table. func istable(v any) bool { _, ok := v.(map[string]any) diff --git a/pkg/cmd/testdata/output/issue-9027.txt b/pkg/cmd/testdata/output/issue-9027.txt index eb19fc383..1227336e4 100644 --- a/pkg/cmd/testdata/output/issue-9027.txt +++ b/pkg/cmd/testdata/output/issue-9027.txt @@ -2,11 +2,15 @@ # Source: issue-9027/charts/subchart/templates/values.yaml global: hash: + key1: 1 + key2: 2 key3: 13 key4: 4 key5: 5 key6: 6 hash: + key1: 1 + key2: 2 key3: 13 key4: 4 key5: 5 @@ -15,17 +19,19 @@ hash: # Source: issue-9027/templates/values.yaml global: hash: - key1: null - key2: null key3: 13 subchart: global: hash: + key1: 1 + key2: 2 key3: 13 key4: 4 key5: 5 key6: 6 hash: + key1: 1 + key2: 2 key3: 13 key4: 4 key5: 5 From 52fc971da37cf34aa26e7d7c460f2430dfb01b26 Mon Sep 17 00:00:00 2001 From: Johannes Lohmer Date: Mon, 30 Mar 2026 10:15:12 +0200 Subject: [PATCH 273/571] test(values): Add test for nil cleanup in partially overridden subchart maps Signed-off-by: Johannes Lohmer --- pkg/chart/common/util/coalesce_test.go | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index f7bf541a2..252ef11ec 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -883,3 +883,48 @@ func TestCoalesceValuesSubchartNilDoesNotShadowGlobal(t *testing.T) { _, ok = ingress["feature"] is.False(ok, "Expected ingress.feature (nil from chart defaults) to be removed so global can be used via pluck, but it is still present") } + +// TestCoalesceValuesSubchartNilCleanedWhenUserPartiallyOverrides tests that nil +// values in subchart defaults are cleaned even when the user partially overrides +// the same map. Regression test for the coalesceTablesFullKey merge path. +func TestCoalesceValuesSubchartNilCleanedWhenUserPartiallyOverrides(t *testing.T) { + is := assert.New(t) + + subchart := &chart.Chart{ + Metadata: &chart.Metadata{Name: "child"}, + Values: map[string]any{ + "keyMapping": map[string]any{ + "password": nil, + "format": "bcrypt", + }, + }, + } + + parent := withDeps(&chart.Chart{ + Metadata: &chart.Metadata{Name: "parent"}, + Values: map[string]any{}, + }, subchart) + + // User overrides format but doesn't mention password + vals := map[string]any{ + "child": map[string]any{ + "keyMapping": map[string]any{ + "format": "sha256", + }, + }, + } + + v, err := CoalesceValues(parent, vals) + is.NoError(err) + + childVals, ok := v["child"].(map[string]any) + is.True(ok, "child values should be a map") + + keyMapping, ok := childVals["keyMapping"].(map[string]any) + is.True(ok, "keyMapping should be a map") + + is.Equal("sha256", keyMapping["format"], "User override should be preserved") + + _, ok = keyMapping["password"] + is.False(ok, "Expected keyMapping.password (nil from chart defaults) to be removed even when user partially overrides the map") +} From a8e249714f5311b9aff44c4bd2bfc433ab1ab952 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Wed, 1 Apr 2026 19:25:09 +0100 Subject: [PATCH 274/571] Update pkg/chart/common/util/coalesce.go Signed-off-by: Evans Mungai --- pkg/chart/common/util/coalesce.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/chart/common/util/coalesce.go b/pkg/chart/common/util/coalesce.go index 90e6640e9..999eeb208 100644 --- a/pkg/chart/common/util/coalesce.go +++ b/pkg/chart/common/util/coalesce.go @@ -356,7 +356,7 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]any, prefix strin return dst } -// cleanNilValues recursively removes nil entries from a map so that chart +// cleanNilValues recursively removes nil entries in-place from a map so that chart // default nils don't leak into the coalesced result. func cleanNilValues(m map[string]any) { for key, val := range m { From 48e2b7ddd4e960b768fe5daee34a33cb89852a6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 22:12:06 +0000 Subject: [PATCH 275/571] chore(deps): bump github.com/mattn/go-shellwords from 1.0.12 to 1.0.13 Bumps [github.com/mattn/go-shellwords](https://github.com/mattn/go-shellwords) from 1.0.12 to 1.0.13. - [Commits](https://github.com/mattn/go-shellwords/compare/v1.0.12...v1.0.13) --- updated-dependencies: - dependency-name: github.com/mattn/go-shellwords dependency-version: 1.0.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e5fcf6c02..1b1f9b4e3 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/gosuri/uitable v0.0.4 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.12.3 - github.com/mattn/go-shellwords v1.0.12 + github.com/mattn/go-shellwords v1.0.13 github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 diff --git a/go.sum b/go.sum index 51f77f70b..ab994625e 100644 --- a/go.sum +++ b/go.sum @@ -206,8 +206,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= -github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-shellwords v1.0.13 h1:DC0OMEpGjm6LfNFU4ckYcvbQKyp2vE8atyFGXNtDcf4= +github.com/mattn/go-shellwords v1.0.13/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= From 265c5eb530a36ec651e79ecf4d37ba2f098b7e59 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Wed, 15 Apr 2026 14:33:27 +0100 Subject: [PATCH 276/571] fix(templating): SplitManifests must preserve line endings for downstream YAML parsers (#31952) * fix(templating): SplitManifests must preserve line endings for downstream YAML parsers Signed-off-by: Matheus Pimenta * Address copilot comment about skipping empty docs Signed-off-by: Matheus Pimenta * Port fix to release v2 Signed-off-by: Matheus Pimenta * Address copilot comments Signed-off-by: Matheus Pimenta --------- Signed-off-by: Matheus Pimenta --- internal/release/v2/util/manifest.go | 18 +- internal/release/v2/util/manifest_test.go | 480 ++++++++++++- pkg/action/action_test.go | 670 +++++++++++++++++- pkg/action/testdata/rbac.txt | 1 + .../install-dry-run-with-secret-hidden.txt | 1 + .../output/install-dry-run-with-secret.txt | 2 + pkg/cmd/testdata/output/issue-9027.txt | 1 + pkg/cmd/testdata/output/object-order.txt | 1 + .../output/template-name-template.txt | 7 + pkg/cmd/testdata/output/template-set.txt | 7 + .../testdata/output/template-skip-tests.txt | 5 + .../output/template-subchart-cm-set-file.txt | 7 + .../output/template-subchart-cm-set.txt | 7 + .../testdata/output/template-subchart-cm.txt | 7 + .../testdata/output/template-values-files.txt | 7 + .../output/template-with-api-version.txt | 7 + .../testdata/output/template-with-crds.txt | 7 + .../output/template-with-kube-version.txt | 7 + pkg/cmd/testdata/output/template.txt | 7 + pkg/release/v1/util/manifest.go | 9 +- pkg/release/v1/util/manifest_test.go | 480 ++++++++++++- 21 files changed, 1691 insertions(+), 47 deletions(-) diff --git a/internal/release/v2/util/manifest.go b/internal/release/v2/util/manifest.go index 20d097d9b..5dbcdaea5 100644 --- a/internal/release/v2/util/manifest.go +++ b/internal/release/v2/util/manifest.go @@ -21,6 +21,7 @@ import ( "regexp" "strconv" "strings" + "unicode" ) // SimpleHead defines what the structure of the head of a manifest file @@ -35,7 +36,16 @@ type SimpleHead struct { var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") -// SplitManifests takes a string of manifest and returns a map contains individual manifests +// SplitManifests takes a manifest string and returns a map containing individual manifests. +// +// **Note for Chart API v3**: This function (due to the regex above) has allowed _WRONG_ +// Go templates to be defined inside charts across the years. The generated text from Go +// templates may contain `---apiVersion: v1`, and this function magically splits this back +// to `---\napiVersion: v1`. This has caused issues recently after Helm 4 introduced +// kio.ParseAll to inject annotations when post-renderers are used. In Chart API v3, +// we should kill this regex with fire (or change it) and expose charts doing the wrong +// thing Go template-wise. Helm should say a big _NO_ to charts doing the wrong thing, +// with or without post-renderers. func SplitManifests(bigFile string) map[string]string { // Basically, we're quickly splitting a stream of YAML documents into an // array of YAML docs. The file name is just a place holder, but should be @@ -44,15 +54,15 @@ func SplitManifests(bigFile string) map[string]string { tpl := "manifest-%d" res := map[string]string{} // Making sure that any extra whitespace in YAML stream doesn't interfere in splitting documents correctly. - bigFileTmp := strings.TrimSpace(bigFile) + bigFileTmp := strings.TrimLeftFunc(bigFile, unicode.IsSpace) docs := sep.Split(bigFileTmp, -1) var count int for _, d := range docs { - if d == "" { + if strings.TrimSpace(d) == "" { continue } - d = strings.TrimSpace(d) + d = strings.TrimLeftFunc(d, unicode.IsSpace) res[fmt.Sprintf(tpl, count)] = d count = count + 1 } diff --git a/internal/release/v2/util/manifest_test.go b/internal/release/v2/util/manifest_test.go index 7fd332fbc..72b095390 100644 --- a/internal/release/v2/util/manifest_test.go +++ b/internal/release/v2/util/manifest_test.go @@ -21,7 +21,15 @@ import ( "testing" ) -const mockManifestFile = ` +func TestSplitManifests(t *testing.T) { + tests := []struct { + name string + input string + expected map[string]string + }{ + { + name: "single doc with leading separator and whitespace", + input: ` --- apiVersion: v1 @@ -35,9 +43,9 @@ spec: - name: nemo-test image: fake-image cmd: fake-command -` - -const expectedManifest = `apiVersion: v1 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 kind: Pod metadata: name: finding-nemo, @@ -47,15 +55,463 @@ spec: containers: - name: nemo-test image: fake-image - cmd: fake-command` + cmd: fake-command +`, + }, + }, + { + name: "empty input", + input: "", + expected: map[string]string{}, + }, + { + name: "whitespace only", + input: " \n\n \n", + expected: map[string]string{}, + }, + { + name: "whitespace-only doc after separator is skipped", + input: "---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1\n---\n \n", + expected: map[string]string{ + "manifest-0": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1", + }, + }, + { + name: "single doc no separator", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +`, + }, + }, + { + name: "two docs with proper separator", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1`, + "manifest-1": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +`, + }, + }, -func TestSplitManifest(t *testing.T) { - manifests := SplitManifests(mockManifestFile) - if len(manifests) != 1 { - t.Errorf("Expected 1 manifest, got %v", len(manifests)) + // Block scalar chomping indicator tests using | (clip), |- (strip), and |+ (keep) + // inputs with 0, 1, and 2 trailing newlines after the block content. + // Note: the emitter may normalize the output chomping indicator when the + // trailing newline count makes another indicator equivalent for the result. + + // | (clip) input — clips trailing newlines to exactly one, though with + // 0 trailing newlines the emitted output may normalize to |-. + { + name: "block scalar clip (|) with 0 trailing newlines", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello`, + }, + }, + { + name: "block scalar clip (|) with 1 trailing newline", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello +`, + }, + }, + { + name: "block scalar clip (|) with 2 trailing newlines", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello + +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello + +`, + }, + }, + + // |- (strip) + { + name: "block scalar strip (|-) with 0 trailing newlines", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello`, + }, + }, + { + name: "block scalar strip (|-) with 1 trailing newline", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello +`, + }, + }, + { + name: "block scalar strip (|-) with 2 trailing newlines", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello + +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello + +`, + }, + }, + + // |+ (keep) + { + name: "block scalar keep (|+) with 0 trailing newlines", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello`, + }, + }, + { + name: "block scalar keep (|+) with 1 trailing newline", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello +`, + }, + }, + { + name: "block scalar keep (|+) with 2 trailing newlines", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello + +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello + +`, + }, + }, + + // Multi-doc with block scalars: the regex consumes \s*\n before ---, + // so trailing newlines from non-last docs are stripped. + { + name: "multi-doc block scalar clip (|) before separator", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello`, + "manifest-1": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +`, + }, + }, + { + name: "multi-doc block scalar keep (|+) with 2 trailing newlines before separator", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello + + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello`, + "manifest-1": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +`, + }, + }, + + // **Note for Chart API v3**: The following tests exercise the lenient + // regex that splits `---apiVersion` back into separate documents. + // In Chart API v3, these inputs should return an _ERROR_ instead. + // See the comment on the SplitManifests function for more details. + { + name: "leading glued separator (---apiVersion)", + input: ` +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +`, + }, + }, + { + name: "mid-content glued separator (---apiVersion)", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1`, + "manifest-1": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +`, + }, + }, + { + name: "multiple glued separators", + input: ` +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm3 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1`, + "manifest-1": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2`, + "manifest-2": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm3 +`, + }, + }, + { + name: "mixed glued and proper separators", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm3 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1`, + "manifest-1": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2`, + "manifest-2": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm3 +`, + }, + }, } - expected := map[string]string{"manifest-0": expectedManifest} - if !reflect.DeepEqual(manifests, expected) { - t.Errorf("Expected %v, got %v", expected, manifests) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := SplitManifests(tt.input) + if !reflect.DeepEqual(result, tt.expected) { + t.Errorf("SplitManifests() =\n%v\nwant:\n%v", result, tt.expected) + } + }) } } diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index a2b170206..d6575a791 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -418,7 +418,8 @@ func TestAnnotateAndMerge(t *testing.T) { { name: "single file with single manifest", files: map[string]string{ - "templates/configmap.yaml": `apiVersion: v1 + "templates/configmap.yaml": ` +apiVersion: v1 kind: ConfigMap metadata: name: test-cm @@ -438,13 +439,15 @@ data: { name: "multiple files with multiple manifests", files: map[string]string{ - "templates/configmap.yaml": `apiVersion: v1 + "templates/configmap.yaml": ` +apiVersion: v1 kind: ConfigMap metadata: name: test-cm data: key: value`, - "templates/secret.yaml": `apiVersion: v1 + "templates/secret.yaml": ` +apiVersion: v1 kind: Secret metadata: name: test-secret @@ -473,7 +476,8 @@ data: { name: "file with multiple manifests", files: map[string]string{ - "templates/multi.yaml": `apiVersion: v1 + "templates/multi.yaml": ` +apiVersion: v1 kind: ConfigMap metadata: name: test-cm1 @@ -509,7 +513,8 @@ data: { name: "partials and empty files are removed", files: map[string]string{ - "templates/cm.yaml": `apiVersion: v1 + "templates/cm.yaml": ` +apiVersion: v1 kind: ConfigMap metadata: name: test-cm1 @@ -531,14 +536,16 @@ metadata: { name: "empty file", files: map[string]string{ - "templates/empty.yaml": "", + "templates/empty.yaml": ` +`, }, expected: ``, }, { name: "invalid yaml", files: map[string]string{ - "templates/invalid.yaml": `invalid: yaml: content: + "templates/invalid.yaml": ` +invalid: yaml: content: - malformed`, }, expectedError: "parsing templates/invalid.yaml", @@ -546,7 +553,12 @@ metadata: { name: "leading doc separator glued to content by template whitespace trimming", files: map[string]string{ - "templates/service.yaml": "---apiVersion: v1\nkind: Service\nmetadata:\n name: test-svc\n", + "templates/service.yaml": ` +---apiVersion: v1 +kind: Service +metadata: + name: test-svc +`, }, expected: `apiVersion: v1 kind: Service @@ -559,7 +571,13 @@ metadata: { name: "leading doc separator on its own line", files: map[string]string{ - "templates/service.yaml": "---\napiVersion: v1\nkind: Service\nmetadata:\n name: test-svc\n", + "templates/service.yaml": ` +--- +apiVersion: v1 +kind: Service +metadata: + name: test-svc +`, }, expected: `apiVersion: v1 kind: Service @@ -572,7 +590,14 @@ metadata: { name: "multiple leading doc separators", files: map[string]string{ - "templates/service.yaml": "---\n---\napiVersion: v1\nkind: Service\nmetadata:\n name: test-svc\n", + "templates/service.yaml": ` +--- +--- +apiVersion: v1 +kind: Service +metadata: + name: test-svc +`, }, expected: `apiVersion: v1 kind: Service @@ -585,7 +610,16 @@ metadata: { name: "mid-content doc separator glued to content by template whitespace trimming", files: map[string]string{ - "templates/all.yaml": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test-cm\n---apiVersion: v1\nkind: Service\nmetadata:\n name: test-svc\n", + "templates/all.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-cm +---apiVersion: v1 +kind: Service +metadata: + name: test-svc +`, }, expected: `apiVersion: v1 kind: ConfigMap @@ -631,7 +665,7 @@ metadata: annotations: postrenderer.helm.sh/postrender-filename: 'templates/configmap.yaml' data: - ca.crt: |- + ca.crt: | ------BEGIN CERTIFICATE------ MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zAKBggqhkjOPQQDAzASMRAw DgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYwMDAw @@ -667,7 +701,7 @@ metadata: annotations: postrenderer.helm.sh/postrender-filename: 'templates/configmap.yaml' data: - config: |- + config: | # --------------------------------------------------------------------------- [section] key = value @@ -694,7 +728,7 @@ metadata: annotations: postrenderer.helm.sh/postrender-filename: 'templates/dashboard.yaml' data: - dashboard.json: |- + dashboard.json: | {"options":{"---------":{"color":"#292929","text":"N/A"}}} `, }, @@ -933,6 +967,605 @@ metadata: name: cm-12 annotations: postrenderer.helm.sh/postrender-filename: 'templates/many.yaml' +`, + }, + + // Block scalar chomping indicator tests using | (clip), |- (strip), and |+ (keep) + // inputs with 0, 1, and 2 trailing newlines after the block content. + // Note: the emitter may normalize the output chomping indicator when the + // trailing newline count makes another indicator equivalent for the result. + + // | (clip) input — clips trailing newlines to exactly one, though with + // 0 trailing newlines the emitted output may normalize to |-. + { + name: "block scalar clip (|) with 0 trailing newlines", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: |- + hello +`, + }, + { + name: "block scalar clip (|) with 1 trailing newline", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello +`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: | + hello +`, + }, + { + name: "block scalar clip (|) with 2 trailing newlines", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello + +`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: | + hello +`, + }, + + // |- (strip) — strips all trailing newlines + { + name: "block scalar strip (|-) with 0 trailing newlines", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: |- + hello +`, + }, + { + name: "block scalar strip (|-) with 1 trailing newline", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello +`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: |- + hello +`, + }, + { + name: "block scalar strip (|-) with 2 trailing newlines", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello + +`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: |- + hello +`, + }, + + // |+ (keep) — preserves all trailing newlines + { + name: "block scalar keep (|+) with 0 trailing newlines", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: |- + hello +`, + }, + { + name: "block scalar keep (|+) with 1 trailing newline", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello +`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: | + hello +`, + }, + { + name: "block scalar keep (|+) with 2 trailing newlines", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello + +`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: |+ + hello + +`, + }, + + // Multi-doc tests: block scalar doc is NOT the last document. + // SplitManifests' regex consumes \s*\n before ---, so trailing + // newlines from non-last docs are always stripped. + + // | (clip) in multi-doc (first doc) + { + name: "multi-doc block scalar clip (|) with 0 trailing newlines", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +data: + val: simple`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: |- + hello +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + val: simple +`, + }, + { + name: "multi-doc block scalar clip (|) with 1 trailing newline", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +data: + val: simple`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: |- + hello +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + val: simple +`, + }, + { + name: "multi-doc block scalar clip (|) with 2 trailing newlines", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello + + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +data: + val: simple`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: |- + hello +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + val: simple +`, + }, + + // |- (strip) in multi-doc (first doc) + { + name: "multi-doc block scalar strip (|-) with 0 trailing newlines", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +data: + val: simple`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: |- + hello +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + val: simple +`, + }, + { + name: "multi-doc block scalar strip (|-) with 1 trailing newline", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +data: + val: simple`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: |- + hello +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + val: simple +`, + }, + { + name: "multi-doc block scalar strip (|-) with 2 trailing newlines", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello + + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +data: + val: simple`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: |- + hello +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + val: simple +`, + }, + + // |+ (keep) in multi-doc (first doc) + { + name: "multi-doc block scalar keep (|+) with 0 trailing newlines", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +data: + val: simple`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: |- + hello +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + val: simple +`, + }, + { + name: "multi-doc block scalar keep (|+) with 1 trailing newline", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +data: + val: simple`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: |- + hello +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + val: simple +`, + }, + { + name: "multi-doc block scalar keep (|+) with 2 trailing newlines", + files: map[string]string{ + "templates/cm.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello + + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +data: + val: simple`, + }, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + key: |- + hello +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 + annotations: + postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' +data: + val: simple `, }, } @@ -1201,12 +1834,15 @@ func TestRenderResources_PostRenderer_Success(t *testing.T) { expectedBuf := `--- # Source: yellow/templates/foodpie foodpie: world + --- # Source: yellow/templates/with-partials yellow: Earth + --- # Source: yellow/templates/yellow yellow: world + ` expectedHook := `kind: ConfigMap metadata: @@ -1214,7 +1850,8 @@ metadata: annotations: "helm.sh/hook": post-install,pre-delete,post-upgrade data: - name: value` + name: value +` assert.Equal(t, expectedBuf, buf.String()) assert.Len(t, hooks, 1) @@ -1319,14 +1956,17 @@ func TestRenderResources_PostRenderer_Integration(t *testing.T) { # Source: hello/templates/goodbye goodbye: world color: blue + --- # Source: hello/templates/hello hello: world color: blue + --- # Source: hello/templates/with-partials hello: Earth color: blue + ` assert.Contains(t, output, "color: blue") assert.Equal(t, 3, strings.Count(output, "color: blue")) diff --git a/pkg/action/testdata/rbac.txt b/pkg/action/testdata/rbac.txt index 0cb15b868..91938d5cc 100644 --- a/pkg/action/testdata/rbac.txt +++ b/pkg/action/testdata/rbac.txt @@ -23,3 +23,4 @@ subjects: - kind: ServiceAccount name: schedule-agents namespace: spaced + diff --git a/pkg/cmd/testdata/output/install-dry-run-with-secret-hidden.txt b/pkg/cmd/testdata/output/install-dry-run-with-secret-hidden.txt index eb770967f..c2219d8c4 100644 --- a/pkg/cmd/testdata/output/install-dry-run-with-secret-hidden.txt +++ b/pkg/cmd/testdata/output/install-dry-run-with-secret-hidden.txt @@ -19,3 +19,4 @@ metadata: data: foo: bar + diff --git a/pkg/cmd/testdata/output/install-dry-run-with-secret.txt b/pkg/cmd/testdata/output/install-dry-run-with-secret.txt index d22c1437f..62bd78018 100644 --- a/pkg/cmd/testdata/output/install-dry-run-with-secret.txt +++ b/pkg/cmd/testdata/output/install-dry-run-with-secret.txt @@ -15,6 +15,7 @@ metadata: name: test-secret stringData: foo: bar + --- # Source: chart-with-secret/templates/configmap.yaml apiVersion: v1 @@ -24,3 +25,4 @@ metadata: data: foo: bar + diff --git a/pkg/cmd/testdata/output/issue-9027.txt b/pkg/cmd/testdata/output/issue-9027.txt index 1227336e4..f43032499 100644 --- a/pkg/cmd/testdata/output/issue-9027.txt +++ b/pkg/cmd/testdata/output/issue-9027.txt @@ -15,6 +15,7 @@ hash: key4: 4 key5: 5 key6: 6 + --- # Source: issue-9027/templates/values.yaml global: diff --git a/pkg/cmd/testdata/output/object-order.txt b/pkg/cmd/testdata/output/object-order.txt index 307f928f2..1ff39f33c 100644 --- a/pkg/cmd/testdata/output/object-order.txt +++ b/pkg/cmd/testdata/output/object-order.txt @@ -155,6 +155,7 @@ spec: policyTypes: - Egress - Ingress + --- # Source: object-order/templates/01-a.yml # 4 (Deployment should come after all NetworkPolicy manifests, since 'helm template' outputs in install order) diff --git a/pkg/cmd/testdata/output/template-name-template.txt b/pkg/cmd/testdata/output/template-name-template.txt index 9406048dd..b1077012e 100644 --- a/pkg/cmd/testdata/output/template-name-template.txt +++ b/pkg/cmd/testdata/output/template-name-template.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -14,6 +15,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -28,6 +30,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -45,6 +48,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -62,6 +66,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -93,6 +98,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -112,3 +118,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-set.txt b/pkg/cmd/testdata/output/template-set.txt index 4040991cf..1ecb8707b 100644 --- a/pkg/cmd/testdata/output/template-set.txt +++ b/pkg/cmd/testdata/output/template-set.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -14,6 +15,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -28,6 +30,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -45,6 +48,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -62,6 +66,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -93,6 +98,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -112,3 +118,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-skip-tests.txt b/pkg/cmd/testdata/output/template-skip-tests.txt index 5c907b563..4c5af8df3 100644 --- a/pkg/cmd/testdata/output/template-skip-tests.txt +++ b/pkg/cmd/testdata/output/template-skip-tests.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -14,6 +15,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -28,6 +30,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -45,6 +48,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -62,6 +66,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 diff --git a/pkg/cmd/testdata/output/template-subchart-cm-set-file.txt b/pkg/cmd/testdata/output/template-subchart-cm-set-file.txt index 56844e292..227d05903 100644 --- a/pkg/cmd/testdata/output/template-subchart-cm-set-file.txt +++ b/pkg/cmd/testdata/output/template-subchart-cm-set-file.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/configmap.yaml apiVersion: v1 @@ -22,6 +23,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -36,6 +38,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -53,6 +56,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -70,6 +74,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -101,6 +106,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -120,3 +126,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-subchart-cm-set.txt b/pkg/cmd/testdata/output/template-subchart-cm-set.txt index e52f7c234..dd8be4db9 100644 --- a/pkg/cmd/testdata/output/template-subchart-cm-set.txt +++ b/pkg/cmd/testdata/output/template-subchart-cm-set.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/configmap.yaml apiVersion: v1 @@ -22,6 +23,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -36,6 +38,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -53,6 +56,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -70,6 +74,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -101,6 +106,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -120,3 +126,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-subchart-cm.txt b/pkg/cmd/testdata/output/template-subchart-cm.txt index 9cc9e2296..c4600a798 100644 --- a/pkg/cmd/testdata/output/template-subchart-cm.txt +++ b/pkg/cmd/testdata/output/template-subchart-cm.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/configmap.yaml apiVersion: v1 @@ -22,6 +23,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -36,6 +38,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -53,6 +56,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -70,6 +74,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -101,6 +106,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -120,3 +126,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-values-files.txt b/pkg/cmd/testdata/output/template-values-files.txt index 4040991cf..1ecb8707b 100644 --- a/pkg/cmd/testdata/output/template-values-files.txt +++ b/pkg/cmd/testdata/output/template-values-files.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -14,6 +15,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -28,6 +30,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -45,6 +48,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -62,6 +66,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -93,6 +98,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -112,3 +118,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-with-api-version.txt b/pkg/cmd/testdata/output/template-with-api-version.txt index 8b6074cdb..ae726e624 100644 --- a/pkg/cmd/testdata/output/template-with-api-version.txt +++ b/pkg/cmd/testdata/output/template-with-api-version.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -14,6 +15,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -28,6 +30,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -45,6 +48,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -62,6 +66,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -95,6 +100,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -114,3 +120,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-with-crds.txt b/pkg/cmd/testdata/output/template-with-crds.txt index 256fc7c3b..1d63265ec 100644 --- a/pkg/cmd/testdata/output/template-with-crds.txt +++ b/pkg/cmd/testdata/output/template-with-crds.txt @@ -21,6 +21,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -31,6 +32,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -45,6 +47,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -62,6 +65,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -79,6 +83,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -110,6 +115,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -129,3 +135,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-with-kube-version.txt b/pkg/cmd/testdata/output/template-with-kube-version.txt index 9d326f328..2c42e2e84 100644 --- a/pkg/cmd/testdata/output/template-with-kube-version.txt +++ b/pkg/cmd/testdata/output/template-with-kube-version.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -14,6 +15,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -28,6 +30,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -45,6 +48,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -62,6 +66,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -93,6 +98,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -112,3 +118,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template.txt b/pkg/cmd/testdata/output/template.txt index 58c480b47..ddbfebe9d 100644 --- a/pkg/cmd/testdata/output/template.txt +++ b/pkg/cmd/testdata/output/template.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -14,6 +15,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -28,6 +30,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -45,6 +48,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -62,6 +66,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -93,6 +98,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -112,3 +118,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/release/v1/util/manifest.go b/pkg/release/v1/util/manifest.go index 3160599bc..fa26f6256 100644 --- a/pkg/release/v1/util/manifest.go +++ b/pkg/release/v1/util/manifest.go @@ -21,6 +21,7 @@ import ( "regexp" "strconv" "strings" + "unicode" ) // SimpleHead defines what the structure of the head of a manifest file @@ -35,7 +36,7 @@ type SimpleHead struct { var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") -// SplitManifests takes a string of manifest and returns a map contains individual manifests +// SplitManifests takes a manifest string and returns a map containing individual manifests. // // **Note for Chart API v3**: This function (due to the regex above) has allowed _WRONG_ // Go templates to be defined inside charts across the years. The generated text from Go @@ -53,15 +54,15 @@ func SplitManifests(bigFile string) map[string]string { tpl := "manifest-%d" res := map[string]string{} // Making sure that any extra whitespace in YAML stream doesn't interfere in splitting documents correctly. - bigFileTmp := strings.TrimSpace(bigFile) + bigFileTmp := strings.TrimLeftFunc(bigFile, unicode.IsSpace) docs := sep.Split(bigFileTmp, -1) var count int for _, d := range docs { - if d == "" { + if strings.TrimSpace(d) == "" { continue } - d = strings.TrimSpace(d) + d = strings.TrimLeftFunc(d, unicode.IsSpace) res[fmt.Sprintf(tpl, count)] = d count = count + 1 } diff --git a/pkg/release/v1/util/manifest_test.go b/pkg/release/v1/util/manifest_test.go index 754ac1367..516ac42d7 100644 --- a/pkg/release/v1/util/manifest_test.go +++ b/pkg/release/v1/util/manifest_test.go @@ -21,7 +21,15 @@ import ( "testing" ) -const mockManifestFile = ` +func TestSplitManifests(t *testing.T) { + tests := []struct { + name string + input string + expected map[string]string + }{ + { + name: "single doc with leading separator and whitespace", + input: ` --- apiVersion: v1 @@ -35,9 +43,9 @@ spec: - name: nemo-test image: fake-image cmd: fake-command -` - -const expectedManifest = `apiVersion: v1 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 kind: Pod metadata: name: finding-nemo, @@ -47,15 +55,463 @@ spec: containers: - name: nemo-test image: fake-image - cmd: fake-command` + cmd: fake-command +`, + }, + }, + { + name: "empty input", + input: "", + expected: map[string]string{}, + }, + { + name: "whitespace only", + input: " \n\n \n", + expected: map[string]string{}, + }, + { + name: "whitespace-only doc after separator is skipped", + input: "---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1\n---\n \n", + expected: map[string]string{ + "manifest-0": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1", + }, + }, + { + name: "single doc no separator", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +`, + }, + }, + { + name: "two docs with proper separator", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1`, + "manifest-1": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +`, + }, + }, -func TestSplitManifest(t *testing.T) { - manifests := SplitManifests(mockManifestFile) - if len(manifests) != 1 { - t.Errorf("Expected 1 manifest, got %v", len(manifests)) + // Block scalar chomping indicator tests using | (clip), |- (strip), and |+ (keep) + // inputs with 0, 1, and 2 trailing newlines after the block content. + // Note: the emitter may normalize the output chomping indicator when the + // trailing newline count makes another indicator equivalent for the result. + + // | (clip) input — clips trailing newlines to exactly one, though with + // 0 trailing newlines the emitted output may normalize to |-. + { + name: "block scalar clip (|) with 0 trailing newlines", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello`, + }, + }, + { + name: "block scalar clip (|) with 1 trailing newline", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello +`, + }, + }, + { + name: "block scalar clip (|) with 2 trailing newlines", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello + +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello + +`, + }, + }, + + // |- (strip) + { + name: "block scalar strip (|-) with 0 trailing newlines", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello`, + }, + }, + { + name: "block scalar strip (|-) with 1 trailing newline", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello +`, + }, + }, + { + name: "block scalar strip (|-) with 2 trailing newlines", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello + +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |- + hello + +`, + }, + }, + + // |+ (keep) + { + name: "block scalar keep (|+) with 0 trailing newlines", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello`, + }, + }, + { + name: "block scalar keep (|+) with 1 trailing newline", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello +`, + }, + }, + { + name: "block scalar keep (|+) with 2 trailing newlines", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello + +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello + +`, + }, + }, + + // Multi-doc with block scalars: the regex consumes \s*\n before ---, + // so trailing newlines from non-last docs are stripped. + { + name: "multi-doc block scalar clip (|) before separator", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: | + hello`, + "manifest-1": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +`, + }, + }, + { + name: "multi-doc block scalar keep (|+) with 2 trailing newlines before separator", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello + + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + key: |+ + hello`, + "manifest-1": `apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +`, + }, + }, + + // **Note for Chart API v3**: The following tests exercise the lenient + // regex that splits `---apiVersion` back into separate documents. + // In Chart API v3, these inputs should return an _ERROR_ instead. + // See the comment on the SplitManifests function for more details. + { + name: "leading glued separator (---apiVersion)", + input: ` +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +`, + }, + }, + { + name: "mid-content glued separator (---apiVersion)", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1`, + "manifest-1": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +`, + }, + }, + { + name: "multiple glued separators", + input: ` +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm3 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1`, + "manifest-1": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2`, + "manifest-2": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm3 +`, + }, + }, + { + name: "mixed glued and proper separators", + input: ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +---apiVersion: v1 +kind: ConfigMap +metadata: + name: cm3 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1`, + "manifest-1": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2`, + "manifest-2": `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm3 +`, + }, + }, } - expected := map[string]string{"manifest-0": expectedManifest} - if !reflect.DeepEqual(manifests, expected) { - t.Errorf("Expected %v, got %v", expected, manifests) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := SplitManifests(tt.input) + if !reflect.DeepEqual(result, tt.expected) { + t.Errorf("SplitManifests() =\n%v\nwant:\n%v", result, tt.expected) + } + }) } } From 934ace35dfaef9eeb9997bf1ee385db0986daecc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:33:33 +0000 Subject: [PATCH 277/571] chore(deps): bump github/codeql-action from 4.35.1 to 4.35.2 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.1 to 4.35.2. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/c10b8064de6f491fea524254123dbe5e09572f13...95e58e9a2cdfd71adc6e0353d5c52f41a045d225) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.35.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 7916808e7..972602fea 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -47,7 +47,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # pinv4.35.1 + uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # pinv4.35.2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -58,7 +58,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # pinv4.35.1 + uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # pinv4.35.2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -72,4 +72,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # pinv4.35.1 + uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # pinv4.35.2 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 75406c17a..41e2f1254 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 + uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 with: sarif_file: results.sarif From 775e794319639f5c1e6b40448ce15ad3cc10d4e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 21:35:02 +0000 Subject: [PATCH 278/571] chore(deps): bump the k8s-io group with 7 updates Bumps the k8s-io group with 7 updates: | Package | From | To | | --- | --- | --- | | [k8s.io/api](https://github.com/kubernetes/api) | `0.35.3` | `0.35.4` | | [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) | `0.35.3` | `0.35.4` | | [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) | `0.35.3` | `0.35.4` | | [k8s.io/apiserver](https://github.com/kubernetes/apiserver) | `0.35.3` | `0.35.4` | | [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime) | `0.35.3` | `0.35.4` | | [k8s.io/client-go](https://github.com/kubernetes/client-go) | `0.35.3` | `0.35.4` | | [k8s.io/kubectl](https://github.com/kubernetes/kubectl) | `0.35.3` | `0.35.4` | Updates `k8s.io/api` from 0.35.3 to 0.35.4 - [Commits](https://github.com/kubernetes/api/compare/v0.35.3...v0.35.4) Updates `k8s.io/apiextensions-apiserver` from 0.35.3 to 0.35.4 - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.35.3...v0.35.4) Updates `k8s.io/apimachinery` from 0.35.3 to 0.35.4 - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.35.3...v0.35.4) Updates `k8s.io/apiserver` from 0.35.3 to 0.35.4 - [Commits](https://github.com/kubernetes/apiserver/compare/v0.35.3...v0.35.4) Updates `k8s.io/cli-runtime` from 0.35.3 to 0.35.4 - [Commits](https://github.com/kubernetes/cli-runtime/compare/v0.35.3...v0.35.4) Updates `k8s.io/client-go` from 0.35.3 to 0.35.4 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.35.3...v0.35.4) Updates `k8s.io/kubectl` from 0.35.3 to 0.35.4 - [Commits](https://github.com/kubernetes/kubectl/compare/v0.35.3...v0.35.4) --- updated-dependencies: - dependency-name: k8s.io/api dependency-version: 0.35.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apiextensions-apiserver dependency-version: 0.35.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apimachinery dependency-version: 0.35.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apiserver dependency-version: 0.35.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/cli-runtime dependency-version: 0.35.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/client-go dependency-version: 0.35.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/kubectl dependency-version: 0.35.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io ... Signed-off-by: dependabot[bot] --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 1b1f9b4e3..7e734a01e 100644 --- a/go.mod +++ b/go.mod @@ -39,14 +39,14 @@ require ( golang.org/x/term v0.42.0 golang.org/x/text v0.36.0 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.35.3 - k8s.io/apiextensions-apiserver v0.35.3 - k8s.io/apimachinery v0.35.3 - k8s.io/apiserver v0.35.3 - k8s.io/cli-runtime v0.35.3 - k8s.io/client-go v0.35.3 + k8s.io/api v0.35.4 + k8s.io/apiextensions-apiserver v0.35.4 + k8s.io/apimachinery v0.35.4 + k8s.io/apiserver v0.35.4 + k8s.io/cli-runtime v0.35.4 + k8s.io/client-go v0.35.4 k8s.io/klog/v2 v2.130.1 - k8s.io/kubectl v0.35.3 + k8s.io/kubectl v0.35.4 oras.land/oras-go/v2 v2.6.0 sigs.k8s.io/controller-runtime v0.23.3 sigs.k8s.io/kustomize/kyaml v0.21.1 @@ -172,7 +172,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/component-base v0.35.3 // indirect + k8s.io/component-base v0.35.4 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect diff --git a/go.sum b/go.sum index ab994625e..9614bfb90 100644 --- a/go.sum +++ b/go.sum @@ -488,26 +488,26 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.35.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ= -k8s.io/api v0.35.3/go.mod h1:9Y9tkBcFwKNq2sxwZTQh1Njh9qHl81D0As56tu42GA4= -k8s.io/apiextensions-apiserver v0.35.3 h1:2fQUhEO7P17sijylbdwt0nBdXP0TvHrHj0KeqHD8FiU= -k8s.io/apiextensions-apiserver v0.35.3/go.mod h1:tK4Kz58ykRpwAEkXUb634HD1ZAegEElktz/B3jgETd8= -k8s.io/apimachinery v0.35.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8= -k8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -k8s.io/apiserver v0.35.3 h1:D2eIcfJ05hEAEewoSDg+05e0aSRwx8Y4Agvd/wiomUI= -k8s.io/apiserver v0.35.3/go.mod h1:JI0n9bHYzSgIxgIrfe21dbduJ9NHzKJ6RchcsmIKWKY= -k8s.io/cli-runtime v0.35.3 h1:UZq4ipNimtzBmhN7PPKbfAdqo8quK0H0UdGl6qAQnqI= -k8s.io/cli-runtime v0.35.3/go.mod h1:O7MUmCqcKSd5xI+O5X7/pRkB5l0O2NIhOdUVwbHLXu4= -k8s.io/client-go v0.35.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg= -k8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c= -k8s.io/component-base v0.35.3 h1:mbKbzoIMy7JDWS/wqZobYW1JDVRn/RKRaoMQHP9c4P0= -k8s.io/component-base v0.35.3/go.mod h1:IZ8LEG30kPN4Et5NeC7vjNv5aU73ku5MS15iZyvyMYk= +k8s.io/api v0.35.4 h1:P7nFYKl5vo9AGUp1Z+Pmd3p2tA7bX2wbFWCvDeRv988= +k8s.io/api v0.35.4/go.mod h1:yl4lqySWOgYJJf9RERXKUwE9g2y+CkuwG+xmcOK8wXU= +k8s.io/apiextensions-apiserver v0.35.4 h1:HeP+Upp7ItdvnyGmub0yoix+2z5+ev4M5cE5TCgtOUU= +k8s.io/apiextensions-apiserver v0.35.4/go.mod h1:ogQlk+stIE8mnoRthSYCwlOS12fVqgWFiErMwPaXA7c= +k8s.io/apimachinery v0.35.4 h1:xtdom9RG7e+yDp71uoXoJDWEE2eOiHgeO4GdBzwWpds= +k8s.io/apimachinery v0.35.4/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc= +k8s.io/apiserver v0.35.4 h1:vtuFqNFmF9bPRdHDL2lpK6qCTPWDreZJL4LRPwVM6ho= +k8s.io/apiserver v0.35.4/go.mod h1:JnBcb+J8kFXKpZkgcbcUnPBBHi4qgBii1I7dLxFY/oo= +k8s.io/cli-runtime v0.35.4 h1:8QRCXSDvopflFNM65Vkkdv42BljPdRSiqf6HFyI1iik= +k8s.io/cli-runtime v0.35.4/go.mod h1:MKLFuZxiJpm87UxjVeQRNy3sCaczHrSOPKN9pinlrM0= +k8s.io/client-go v0.35.4 h1:DN6fyaGuzK64UvnKO5fOA6ymSjvfGAnCAHAR0C66kD8= +k8s.io/client-go v0.35.4/go.mod h1:2Pg9WpsS4NeOpoYTfHHfMxBG8zFMSAUi4O/qoiJC3nY= +k8s.io/component-base v0.35.4 h1:6n1tNJ87johN0Hif0Fs8K2GMthsaUwMqCebUDLYyv7U= +k8s.io/component-base v0.35.4/go.mod h1:qaDJgz5c1KYKla9occFmlJEfPpkuA55s90G509R+PeY= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/kubectl v0.35.3 h1:1KqSYXk/sodU7VeDvK6atX2kAGUZd2QTeR5K7Hb9r9w= -k8s.io/kubectl v0.35.3/go.mod h1:GPHxZqRe+u/i3gTBoVQHeIyq2NilfNPj9hDWeuN3x5s= +k8s.io/kubectl v0.35.4 h1:IHitney6OUeH29rBQnt6Cas6az8HpFeSAohormITNMc= +k8s.io/kubectl v0.35.4/go.mod h1:CGWAaof9ae4vGDAyhnSf1bSQN/U7jiWQHLVbMbLMjRI= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= From db40adb1d13573280b65bc2002df7d75c009235a Mon Sep 17 00:00:00 2001 From: Mohit Date: Sun, 19 Apr 2026 22:45:05 +0530 Subject: [PATCH 279/571] docs: fix grammar and spacing in CONTRIBUTING.md Signed-off-by: Mohit --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0b704aa9a..7aa19972f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,7 @@ chance to try to fix the issue before it is exploited in the wild. Helm v4 development takes place on the `main` branch while Helm v3 is on the `dev-v3` branch. -Helm v3 will continue to receive bug fixes and updates for new Kubernetes releases until July 8th 2026. Security enhancement will still be applied until November 11th 2026. See the blog for more details. +Helm v3 will continue to receive bug fixes and updates for new Kubernetes releases until July 8th 2026. Security enhancements will still be applied until November 11th 2026. See the blog for more details. Bugs should first be fixed on Helm v4 and then backported to Helm v3. Helm v3 (and the `dev-v3` branch) is no longer accepting new features. @@ -162,9 +162,9 @@ There are 5 types of issues (each with their own corresponding [label](#labels)) for future reference. Generally these are questions that are too complex or large to store in the Slack channel or have particular interest to the community as a whole. Depending on the discussion, these can turn into `feature` or `bug` issues. -- `proposal`: Used for items (like this one) that propose a new ideas or functionality that require +- `proposal`: Used for items (like this one) that propose new ideas or functionality that require a larger community discussion. This allows for feedback from others in the community before a - feature is actually developed. This is not needed for small additions. Final word on whether + feature is actually developed. This is not needed for small additions. Final word on whether a feature needs a proposal is up to the core maintainers. All issues that are proposals should both have a label and an issue title of "Proposal: [the rest of the title]." A proposal can become a `feature` and does not require a milestone. From 8f56f24d638612a46f3e23265d06338c1f93bccb Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Tue, 21 Apr 2026 19:11:38 +0100 Subject: [PATCH 280/571] fix(templating): hooks conflicting with templates in post-renderers (#32049) * fix(templating): hooks conflicting with templates in post-renderers Signed-off-by: Matheus Pimenta * fix(templating): allow disabling hooks from postrenderers entirely Signed-off-by: Matheus Pimenta --------- Signed-off-by: Matheus Pimenta --- pkg/action/action.go | 179 ++++++++++++++++++--- pkg/action/action_test.go | 317 ++++++++++++++++++++++++++++++++++++-- pkg/action/install.go | 13 +- pkg/action/upgrade.go | 13 +- 4 files changed, 481 insertions(+), 41 deletions(-) diff --git a/pkg/action/action.go b/pkg/action/action.go index c93950103..8c1888144 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -88,6 +88,33 @@ const ( DryRunServer DryRunStrategy = "server" ) +// PostRenderStrategy determines how hooks and regular templates are passed +// to the configured post-renderer. +type PostRenderStrategy string + +const ( + // PostRenderStrategyCombined sends hooks and regular templates together + // as a single stream to the post-renderer. This is the default in Helm 4. + PostRenderStrategyCombined PostRenderStrategy = "combined" + + // PostRenderStrategySeparate sends hooks and regular templates to the + // post-renderer in independent invocations. This avoids duplicate-resource + // errors from post-renderers that de-duplicate by resource identity + // (for example Kustomize) when the same resource appears in both a hook + // and a regular template. Passing hooks to post-renderers was introduced + // in Helm 4; Helm 3 never did so, which is why the issue only surfaces + // with the Helm 4 combined default. + PostRenderStrategySeparate PostRenderStrategy = "separate" + + // PostRenderStrategyNoHooks sends only regular templates to the + // post-renderer and leaves hooks untouched. This matches the Helm 3 + // behavior and is useful for post-renderers that declare transforms + // targeting template-only resources (for example Kustomize patches + // against a Deployment that exists in templates but not in hooks), + // which would otherwise fail against the hook stream. + PostRenderStrategyNoHooks PostRenderStrategy = "nohooks" +) + // Configuration injects the dependencies that all actions share. type Configuration struct { // RESTClientGetter is an interface that loads Kubernetes clients. @@ -198,7 +225,14 @@ func annotateAndMerge(files map[string]string) (string, error) { // splitAndDeannotate reconstructs individual files from a merged YAML stream, // removing filename annotations and grouping documents by their original filenames. -func splitAndDeannotate(postrendered string) (map[string]string, error) { +// Documents without a filename annotation are assigned a synthesized name of the +// form "generated-by-postrender--.yaml" (or +// "generated-by-postrender-.yaml" when fallbackPrefix is empty). The prefix +// disambiguates fallback filenames across multiple post-render invocations (for +// example when PostRenderStrategySeparate runs the post-renderer once per +// group), so that merging results from different invocations does not collide +// on the same synthetic key. +func splitAndDeannotate(postrendered, fallbackPrefix string) (map[string]string, error) { manifests, err := kio.ParseAll(postrendered) if err != nil { return nil, fmt.Errorf("error parsing YAML: %w", err) @@ -212,7 +246,11 @@ func splitAndDeannotate(postrendered string) (map[string]string, error) { } fname := meta.Annotations[filenameAnnotation] if fname == "" { - fname = fmt.Sprintf("generated-by-postrender-%d.yaml", i) + if fallbackPrefix == "" { + fname = fmt.Sprintf("generated-by-postrender-%d.yaml", i) + } else { + fname = fmt.Sprintf("generated-by-postrender-%s-%d.yaml", fallbackPrefix, i) + } } if err := manifest.PipeE(kyaml.ClearAnnotation(filenameAnnotation)); err != nil { return nil, fmt.Errorf("clearing filename annotation: %w", err) @@ -237,7 +275,7 @@ func splitAndDeannotate(postrendered string) (map[string]string, error) { // TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed // // This code has to do with writing files to disk. -func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrenderer.PostRenderer, interactWithRemote, enableDNS, hideSecret bool) ([]*release.Hook, *bytes.Buffer, string, error) { +func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrenderer.PostRenderer, interactWithRemote, enableDNS, hideSecret bool, postRenderStrategy PostRenderStrategy) ([]*release.Hook, *bytes.Buffer, string, error) { var hs []*release.Hook b := bytes.NewBuffer(nil) @@ -301,29 +339,122 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values, notes := notesBuffer.String() if pr != nil { - // We need to send files to the post-renderer before sorting and splitting - // hooks from manifests. The post-renderer interface expects a stream of - // manifests (similar to what tools like Kustomize and kubectl expect), whereas - // the sorter uses filenames. - // Here, we merge the documents into a stream, post-render them, and then split - // them back into a map of filename -> content. - - // Merge files as stream of documents for sending to post renderer - merged, err := annotateAndMerge(files) - if err != nil { - return hs, b, notes, fmt.Errorf("error merging manifests: %w", err) - } + switch postRenderStrategy { + case PostRenderStrategySeparate, PostRenderStrategyNoHooks: + // Split hooks from manifests before post-rendering. For "separate", + // hooks and templates are sent to the post-renderer as independent + // streams to avoid duplicate-resource errors when the same resource + // appears in both (e.g. a ServiceAccount used by a pre-install hook + // that is also declared in the chart's regular templates). For + // "nohooks", hooks skip the post-renderer entirely, matching the + // Helm 3 behavior. + sortedHooks, sortedManifests, err := releaseutil.SortManifests(files, nil, releaseutil.InstallOrder) + if err != nil { + for name, content := range files { + if strings.TrimSpace(content) == "" { + continue + } + fmt.Fprintf(b, "---\n# Source: %s\n%s\n", name, content) + } + return hs, b, "", err + } - // Run the post renderer - postRendered, err := pr.Run(bytes.NewBufferString(merged)) - if err != nil { - return hs, b, notes, fmt.Errorf("error while running post render on files: %w", err) - } + // Build separate files maps for hooks and manifests. + hookFiles := make(map[string]string) + for _, h := range sortedHooks { + if existing, ok := hookFiles[h.Path]; ok { + hookFiles[h.Path] = existing + "\n---\n" + h.Manifest + } else { + hookFiles[h.Path] = h.Manifest + } + } + manifestFiles := make(map[string]string) + for _, m := range sortedManifests { + if existing, ok := manifestFiles[m.Name]; ok { + manifestFiles[m.Name] = existing + "\n---\n" + m.Content + } else { + manifestFiles[m.Name] = m.Content + } + } - // Use the file list and contents received from the post renderer - files, err = splitAndDeannotate(postRendered.String()) - if err != nil { - return hs, b, notes, fmt.Errorf("error while parsing post rendered output: %w", err) + // Decide which groups to post-render. "nohooks" passes hooks + // through untouched and only post-renders manifests. + groups := []struct { + name string + files map[string]string + postRender bool + }{ + {"hooks", hookFiles, postRenderStrategy == PostRenderStrategySeparate}, + {"manifests", manifestFiles, true}, + } + + files = make(map[string]string) + for _, group := range groups { + if len(group.files) == 0 { + continue + } + + if !group.postRender { + for k, v := range group.files { + if existing, ok := files[k]; ok { + files[k] = existing + "\n---\n" + v + } else { + files[k] = v + } + } + continue + } + + merged, err := annotateAndMerge(group.files) + if err != nil { + return hs, b, notes, fmt.Errorf("error merging %s: %w", group.name, err) + } + + postRendered, err := pr.Run(bytes.NewBufferString(merged)) + if err != nil { + return hs, b, notes, fmt.Errorf("error while running post render on %s: %w", group.name, err) + } + + rendered, err := splitAndDeannotate(postRendered.String(), group.name) + if err != nil { + return hs, b, notes, fmt.Errorf("error while parsing post rendered output for %s: %w", group.name, err) + } + + for k, v := range rendered { + if existing, ok := files[k]; ok { + files[k] = existing + "\n---\n" + v + } else { + files[k] = v + } + } + } + case PostRenderStrategyCombined, "": + // We need to send files to the post-renderer before sorting and splitting + // hooks from manifests. The post-renderer interface expects a stream of + // manifests (similar to what tools like Kustomize and kubectl expect), whereas + // the sorter uses filenames. + // Here, we merge the documents into a stream, post-render them, and then split + // them back into a map of filename -> content. + + // Merge files as stream of documents for sending to post renderer + merged, err := annotateAndMerge(files) + if err != nil { + return hs, b, notes, fmt.Errorf("error merging manifests: %w", err) + } + + // Run the post renderer + postRendered, err := pr.Run(bytes.NewBufferString(merged)) + if err != nil { + return hs, b, notes, fmt.Errorf("error while running post render on files: %w", err) + } + + // Use the file list and contents received from the post renderer + files, err = splitAndDeannotate(postRendered.String(), "") + if err != nil { + return hs, b, notes, fmt.Errorf("error while parsing post rendered output: %w", err) + } + default: + return hs, b, notes, fmt.Errorf("unknown post-render strategy: '%s'", postRenderStrategy) } } diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index d6575a791..54b07273b 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -1722,7 +1722,7 @@ metadata: data: key: value`, expectedFiles: map[string]string{ - "generated-by-postrender-0.yaml": `apiVersion: v1 + "generated-by-postrender-test-0.yaml": `apiVersion: v1 kind: ConfigMap metadata: name: test-cm @@ -1735,7 +1735,7 @@ data: for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - files, err := splitAndDeannotate(tt.input) + files, err := splitAndDeannotate(tt.input, "test") if tt.expectedError != "" { assert.Error(t, err) @@ -1789,7 +1789,7 @@ data: require.NoError(t, err) // Split and deannotate - reconstructed, err := splitAndDeannotate(merged) + reconstructed, err := splitAndDeannotate(merged, "test") require.NoError(t, err) // Compare the results @@ -1824,7 +1824,7 @@ func TestRenderResources_PostRenderer_Success(t *testing.T) { hooks, buf, notes, err := cfg.renderResources( ch, values, "test-release", "", false, false, false, - mockPR, false, false, false, + mockPR, false, false, false, PostRenderStrategyCombined, ) assert.NoError(t, err) @@ -1871,7 +1871,7 @@ func TestRenderResources_PostRenderer_Error(t *testing.T) { _, _, _, err := cfg.renderResources( ch, values, "test-release", "", false, false, false, - mockPR, false, false, false, + mockPR, false, false, false, PostRenderStrategyCombined, ) assert.Error(t, err) @@ -1899,7 +1899,7 @@ func TestRenderResources_PostRenderer_MergeError(t *testing.T) { _, _, _, err := cfg.renderResources( ch, values, "test-release", "", false, false, false, - mockPR, false, false, false, + mockPR, false, false, false, PostRenderStrategyCombined, ) assert.Error(t, err) @@ -1921,7 +1921,7 @@ func TestRenderResources_PostRenderer_SplitError(t *testing.T) { _, _, _, err := cfg.renderResources( ch, values, "test-release", "", false, false, false, - mockPR, false, false, false, + mockPR, false, false, false, PostRenderStrategyCombined, ) assert.Error(t, err) @@ -1942,7 +1942,7 @@ func TestRenderResources_PostRenderer_Integration(t *testing.T) { hooks, buf, notes, err := cfg.renderResources( ch, values, "test-release", "", false, false, false, - mockPR, false, false, false, + mockPR, false, false, false, PostRenderStrategyCombined, ) assert.NoError(t, err) @@ -1981,7 +1981,7 @@ func TestRenderResources_NoPostRenderer(t *testing.T) { hooks, buf, notes, err := cfg.renderResources( ch, values, "test-release", "", false, false, false, - nil, false, false, false, + nil, false, false, false, PostRenderStrategyCombined, ) assert.NoError(t, err) @@ -1990,6 +1990,305 @@ func TestRenderResources_NoPostRenderer(t *testing.T) { assert.Equal(t, "", notes) } +func TestRenderResources_PostRenderer_DuplicateResourceInHookAndTemplate(t *testing.T) { + cfg := actionConfigFixture(t) + + // Simulate a chart where the same ServiceAccount appears both as a + // pre-install hook and as a regular template. This is a valid Helm pattern + // but previously caused post-renderers like Kustomize to fail with + // "may not add resource with an already registered id" because hooks and + // templates were merged into a single stream before post-rendering. + saHook := `apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-app + annotations: + "helm.sh/hook": pre-install + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded` + + saTemplate := `apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-app` + + deployment := `apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-app +spec: + template: + spec: + serviceAccountName: my-app` + + modTime := time.Now() + ch := buildChartWithTemplates([]*common.File{ + {Name: "templates/sa-hook.yaml", ModTime: modTime, Data: []byte(saHook)}, + {Name: "templates/sa.yaml", ModTime: modTime, Data: []byte(saTemplate)}, + {Name: "templates/deployment.yaml", ModTime: modTime, Data: []byte(deployment)}, + }) + + // Use a post-renderer that rejects duplicate resource IDs, similar to + // how Kustomize behaves. We verify that no single post-render call + // receives the ServiceAccount twice. + mockPR := &mockPostRenderer{ + transform: func(content string) string { + count := strings.Count(content, "kind: ServiceAccount") + if count > 1 { + t.Errorf("post-renderer received %d ServiceAccount resources in a single stream, expected at most 1", count) + } + return content + }, + } + + hooks, buf, _, err := cfg.renderResources( + ch, nil, "test-release", "", false, false, false, + mockPR, false, false, false, PostRenderStrategySeparate, + ) + + assert.NoError(t, err) + assert.Len(t, hooks, 1) + assert.Equal(t, "my-app", hooks[0].Name) + assert.Contains(t, buf.String(), "kind: Deployment") + assert.Contains(t, buf.String(), "kind: ServiceAccount") +} + +func TestRenderResources_PostRenderer_CombinedInvokesOnceWithEverything(t *testing.T) { + cfg := actionConfigFixture(t) + + hookManifest := `apiVersion: v1 +kind: ConfigMap +metadata: + name: hook-cm + annotations: + "helm.sh/hook": pre-install` + templateManifest := `apiVersion: v1 +kind: ConfigMap +metadata: + name: template-cm` + + modTime := time.Now() + ch := buildChartWithTemplates([]*common.File{ + {Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(hookManifest)}, + {Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(templateManifest)}, + }) + + var calls int + var lastInput string + mockPR := &mockPostRenderer{ + transform: func(content string) string { + calls++ + lastInput = content + return content + }, + } + + _, _, _, err := cfg.renderResources( + ch, nil, "test-release", "", false, false, false, + mockPR, false, false, false, PostRenderStrategyCombined, + ) + + assert.NoError(t, err) + assert.Equal(t, 1, calls, "combined strategy should invoke the post-renderer exactly once") + assert.Contains(t, lastInput, "hook-cm") + assert.Contains(t, lastInput, "template-cm") +} + +func TestRenderResources_PostRenderer_ZeroValueStrategyActsAsCombined(t *testing.T) { + cfg := actionConfigFixture(t) + + modTime := time.Now() + ch := buildChartWithTemplates([]*common.File{ + {Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1 +kind: ConfigMap +metadata: + name: template-cm`)}, + {Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1 +kind: ConfigMap +metadata: + name: hook-cm + annotations: + "helm.sh/hook": pre-install`)}, + }) + + var calls int + mockPR := &mockPostRenderer{ + transform: func(content string) string { + calls++ + return content + }, + } + + _, _, _, err := cfg.renderResources( + ch, nil, "test-release", "", false, false, false, + mockPR, false, false, false, PostRenderStrategy(""), + ) + + assert.NoError(t, err) + assert.Equal(t, 1, calls, "unset strategy must preserve backwards-compatible combined behavior") +} + +func TestRenderResources_PostRenderer_SeparateSplitsHooksAndTemplates(t *testing.T) { + cfg := actionConfigFixture(t) + + modTime := time.Now() + ch := buildChartWithTemplates([]*common.File{ + {Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1 +kind: ConfigMap +metadata: + name: hook-cm + annotations: + "helm.sh/hook": pre-install`)}, + {Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1 +kind: ConfigMap +metadata: + name: template-cm`)}, + }) + + var inputs []string + mockPR := &mockPostRenderer{ + transform: func(content string) string { + inputs = append(inputs, content) + return content + }, + } + + _, _, _, err := cfg.renderResources( + ch, nil, "test-release", "", false, false, false, + mockPR, false, false, false, PostRenderStrategySeparate, + ) + + assert.NoError(t, err) + assert.Len(t, inputs, 2, "separate strategy should invoke the post-renderer twice when both hooks and templates exist") + for _, in := range inputs { + hasHook := strings.Contains(in, "hook-cm") + hasTemplate := strings.Contains(in, "template-cm") + assert.False(t, hasHook && hasTemplate, "a single post-render invocation must not contain both hook and template resources") + assert.True(t, hasHook || hasTemplate, "each post-render invocation must contain either a hook or a template") + } +} + +func TestRenderResources_PostRenderer_SeparateWithOnlyTemplates(t *testing.T) { + cfg := actionConfigFixture(t) + + modTime := time.Now() + ch := buildChartWithTemplates([]*common.File{ + {Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1 +kind: ConfigMap +metadata: + name: template-cm`)}, + }) + + var calls int + mockPR := &mockPostRenderer{ + transform: func(content string) string { + calls++ + return content + }, + } + + _, _, _, err := cfg.renderResources( + ch, nil, "test-release", "", false, false, false, + mockPR, false, false, false, PostRenderStrategySeparate, + ) + + assert.NoError(t, err) + assert.Equal(t, 1, calls, "separate strategy should skip the empty hook group and invoke the post-renderer only once") +} + +func TestRenderResources_PostRenderer_NoHooksSkipsHooks(t *testing.T) { + cfg := actionConfigFixture(t) + + modTime := time.Now() + ch := buildChartWithTemplates([]*common.File{ + {Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1 +kind: ConfigMap +metadata: + name: hook-cm + annotations: + "helm.sh/hook": pre-install`)}, + {Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1 +kind: ConfigMap +metadata: + name: template-cm`)}, + }) + + var inputs []string + mockPR := &mockPostRenderer{ + transform: func(content string) string { + inputs = append(inputs, content) + return content + }, + } + + hooks, manifestDoc, _, err := cfg.renderResources( + ch, nil, "test-release", "", false, false, false, + mockPR, false, false, false, PostRenderStrategyNoHooks, + ) + + assert.NoError(t, err) + assert.Len(t, inputs, 1, "nohooks strategy should invoke the post-renderer exactly once (for templates only)") + assert.NotContains(t, inputs[0], "hook-cm", "hooks must not be sent to the post-renderer") + assert.Contains(t, inputs[0], "template-cm", "templates must be sent to the post-renderer") + + // Hooks still round-trip through the release so they can execute. + require.Len(t, hooks, 1) + assert.Contains(t, hooks[0].Manifest, "hook-cm") + assert.Contains(t, manifestDoc.String(), "template-cm") +} + +func TestRenderResources_PostRenderer_NoHooksWithOnlyHooks(t *testing.T) { + cfg := actionConfigFixture(t) + + modTime := time.Now() + ch := buildChartWithTemplates([]*common.File{ + {Name: "templates/hook.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1 +kind: ConfigMap +metadata: + name: hook-cm + annotations: + "helm.sh/hook": pre-install`)}, + }) + + var calls int + mockPR := &mockPostRenderer{ + transform: func(content string) string { + calls++ + return content + }, + } + + _, _, _, err := cfg.renderResources( + ch, nil, "test-release", "", false, false, false, + mockPR, false, false, false, PostRenderStrategyNoHooks, + ) + + assert.NoError(t, err) + assert.Equal(t, 0, calls, "nohooks strategy should not invoke the post-renderer when the chart only has hooks") +} + +func TestRenderResources_PostRenderer_UnknownStrategyErrors(t *testing.T) { + cfg := actionConfigFixture(t) + + modTime := time.Now() + ch := buildChartWithTemplates([]*common.File{ + {Name: "templates/cm.yaml", ModTime: modTime, Data: []byte(`apiVersion: v1 +kind: ConfigMap +metadata: + name: template-cm`)}, + }) + + mockPR := &mockPostRenderer{} + + _, _, _, err := cfg.renderResources( + ch, nil, "test-release", "", false, false, false, + mockPR, false, false, false, PostRenderStrategy("bogus"), + ) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "unknown post-render strategy") + assert.Contains(t, err.Error(), "bogus") +} + func TestDetermineReleaseSSAApplyMethod(t *testing.T) { assert.Equal(t, release.ApplyMethodClientSideApply, determineReleaseSSApplyMethod(false)) assert.Equal(t, release.ApplyMethodServerSideApply, determineReleaseSSApplyMethod(true)) diff --git a/pkg/action/install.go b/pkg/action/install.go index 50df13c05..580b8a0cb 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -130,6 +130,10 @@ type Install struct { // TakeOwnership will ignore the check for helm annotations and take ownership of the resources. TakeOwnership bool PostRenderer postrenderer.PostRenderer + // PostRenderStrategy controls how hooks and regular templates are passed + // to the configured post-renderer. See PostRenderStrategy for the + // available modes. Defaults to PostRenderStrategyCombined. + PostRenderStrategy PostRenderStrategy // Lock to control raceconditions when the process receives a SIGTERM Lock sync.Mutex goroutineCount atomic.Int32 @@ -158,9 +162,10 @@ type ChartPathOptions struct { // NewInstall creates a new Install object with the given configuration. func NewInstall(cfg *Configuration) *Install { in := &Install{ - cfg: cfg, - ServerSideApply: true, // Must always match the CLI default. - DryRunStrategy: DryRunNone, + cfg: cfg, + ServerSideApply: true, // Must always match the CLI default. + DryRunStrategy: DryRunNone, + PostRenderStrategy: PostRenderStrategyCombined, } in.registryClient = cfg.RegistryClient @@ -370,7 +375,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st rel := i.createRelease(chrt, vals, i.Labels) var manifestDoc *bytes.Buffer - rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithServer(i.DryRunStrategy), i.EnableDNS, i.HideSecret) + rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithServer(i.DryRunStrategy), i.EnableDNS, i.HideSecret, i.PostRenderStrategy) // Even for errors, attach this if available if manifestDoc != nil { rel.Manifest = manifestDoc.String() diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 103ab4fdb..00939ffa6 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -121,6 +121,10 @@ type Upgrade struct { // If this is non-nil, then after templates are rendered, they will be sent to the // post renderer before sending to the Kubernetes API server. PostRenderer postrenderer.PostRenderer + // PostRenderStrategy controls how hooks and regular templates are passed + // to the configured post-renderer. See PostRenderStrategy for the + // available modes. Defaults to PostRenderStrategyCombined. + PostRenderStrategy PostRenderStrategy // DisableOpenAPIValidation controls whether OpenAPI validation is enforced. DisableOpenAPIValidation bool // Get missing dependencies @@ -141,9 +145,10 @@ type resultMessage struct { // NewUpgrade creates a new Upgrade object with the given configuration. func NewUpgrade(cfg *Configuration) *Upgrade { up := &Upgrade{ - cfg: cfg, - ServerSideApply: "auto", // Must always match the CLI default. - DryRunStrategy: DryRunNone, + cfg: cfg, + ServerSideApply: "auto", // Must always match the CLI default. + DryRunStrategy: DryRunNone, + PostRenderStrategy: PostRenderStrategyCombined, } up.registryClient = cfg.RegistryClient @@ -296,7 +301,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str return nil, nil, false, err } - hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithServer(u.DryRunStrategy), u.EnableDNS, u.HideSecret) + hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithServer(u.DryRunStrategy), u.EnableDNS, u.HideSecret, u.PostRenderStrategy) if err != nil { return nil, nil, false, err } From 1e145ee2b243ef97bce0622cf55da8aa17ebb65f Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Fri, 23 Jan 2026 15:25:09 +0100 Subject: [PATCH 281/571] fix: prevent warning when using version range constraints When using version ranges like ^1 or ~1.10, Helm incorrectly showed warnings about falling back to closest version. Only show the warning when an exact version is requested but not found. Fixes: https://github.com/helm/helm/issues/31757 Signed-off-by: Benoit Tigeot --- pkg/repo/v1/index.go | 8 +++++++- pkg/repo/v1/index_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/pkg/repo/v1/index.go b/pkg/repo/v1/index.go index 3dbdf7dfc..461416a59 100644 --- a/pkg/repo/v1/index.go +++ b/pkg/repo/v1/index.go @@ -175,6 +175,12 @@ func (i IndexFile) SortEntries() { } } +// isVersionRange checks if the version string is a range constraint (e.g., "^1", "~1.10") +// rather than an exact version (e.g., "1.10.0"). +func isVersionRange(version string) bool { + return strings.ContainsAny(version, "^~<>=!*xX") || strings.Contains(version, " || ") || strings.Contains(version, " - ") +} + // Get returns the ChartVersion for the given name. // // If version is empty, this will return the chart with the latest stable version, @@ -215,7 +221,7 @@ func (i IndexFile) Get(name, version string) (*ChartVersion, error) { } if constraint.Check(test) { - if len(version) != 0 { + if len(version) != 0 && !isVersionRange(version) { slog.Warn("unable to find exact version requested; falling back to closest available version", "chart", name, "requested", version, "selected", ver.Version) } return ver, nil diff --git a/pkg/repo/v1/index_test.go b/pkg/repo/v1/index_test.go index 550c8e82c..b01aa5a7d 100644 --- a/pkg/repo/v1/index_test.go +++ b/pkg/repo/v1/index_test.go @@ -718,3 +718,37 @@ func TestLoadIndex_DuplicateChartDeps(t *testing.T) { }) } } + +func TestIsVersionRange(t *testing.T) { + tests := []struct { + version string + expected bool + }{ + {"1.0.0", false}, + {"1.0.0+metadata", false}, + {"^1", true}, + {"^1.2.3", true}, + {"~1.10", true}, + {"~1.10.0", true}, + {">= 1.0.0", true}, + {"> 1.0.0", true}, + {"< 2.0.0", true}, + {"<= 2.0.0", true}, + {"!= 1.0.0", true}, + {"1.*", true}, + {"1.x", true}, + {"1.X", true}, + {"1.0.0 - 2.0.0", true}, + {"^1.0.0 || ^2.0.0", true}, + {">=1.0.0 <2.0.0", true}, + } + + for _, tt := range tests { + t.Run(tt.version, func(t *testing.T) { + got := isVersionRange(tt.version) + if got != tt.expected { + t.Errorf("isVersionRange(%q) = %v, want %v", tt.version, got, tt.expected) + } + }) + } +} From bf78b876c74ee4359a6cebb54e0b97183cdfe129 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Fri, 23 Jan 2026 17:48:35 +0100 Subject: [PATCH 282/571] feat: report in debug the version we select with version range arg Signed-off-by: Benoit Tigeot --- pkg/repo/v1/index.go | 2 ++ pkg/repo/v1/index_test.go | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/pkg/repo/v1/index.go b/pkg/repo/v1/index.go index 461416a59..ccec82b3e 100644 --- a/pkg/repo/v1/index.go +++ b/pkg/repo/v1/index.go @@ -223,6 +223,8 @@ func (i IndexFile) Get(name, version string) (*ChartVersion, error) { if constraint.Check(test) { if len(version) != 0 && !isVersionRange(version) { slog.Warn("unable to find exact version requested; falling back to closest available version", "chart", name, "requested", version, "selected", ver.Version) + } else if len(version) != 0 && isVersionRange(version) { + slog.Debug("selected version matching constraint", "chart", name, "constraint", version, "selected", ver.Version) } return ver, nil } diff --git a/pkg/repo/v1/index_test.go b/pkg/repo/v1/index_test.go index b01aa5a7d..7a3120c89 100644 --- a/pkg/repo/v1/index_test.go +++ b/pkg/repo/v1/index_test.go @@ -726,6 +726,8 @@ func TestIsVersionRange(t *testing.T) { }{ {"1.0.0", false}, {"1.0.0+metadata", false}, + {"v1.19.2", false}, + {"v1", false}, {"^1", true}, {"^1.2.3", true}, {"~1.10", true}, @@ -738,6 +740,8 @@ func TestIsVersionRange(t *testing.T) { {"1.*", true}, {"1.x", true}, {"1.X", true}, + {"v1.x", true}, + {"v1.X", true}, {"1.0.0 - 2.0.0", true}, {"^1.0.0 || ^2.0.0", true}, {">=1.0.0 <2.0.0", true}, From b79d7f18813796f101f66eff6c513ded25edf0ad Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Wed, 4 Feb 2026 22:02:19 +0100 Subject: [PATCH 283/571] fix(version): version range || can has no space From Matt's comment > The check for " || " should remove the spaces and have "||". Spaces around the || aren't required. Signed-off-by: Benoit Tigeot --- pkg/repo/v1/index.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/repo/v1/index.go b/pkg/repo/v1/index.go index ccec82b3e..1ed7c32b6 100644 --- a/pkg/repo/v1/index.go +++ b/pkg/repo/v1/index.go @@ -178,7 +178,7 @@ func (i IndexFile) SortEntries() { // isVersionRange checks if the version string is a range constraint (e.g., "^1", "~1.10") // rather than an exact version (e.g., "1.10.0"). func isVersionRange(version string) bool { - return strings.ContainsAny(version, "^~<>=!*xX") || strings.Contains(version, " || ") || strings.Contains(version, " - ") + return strings.ContainsAny(version, "^~<>=!*xX") || strings.Contains(version, "||") || strings.Contains(version, " - ") } // Get returns the ChartVersion for the given name. From 740174a2b12074f7ca506ff330a918a4ff335c39 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Fri, 24 Apr 2026 09:23:11 +0200 Subject: [PATCH 284/571] fix(version): avoid false range detection on prerelease x/X `isVersionRange` checked for `x`/`X` across the entire version string, misclassifying exact versions like `1.0.0-fix`, `2.0.0-next`, or `1.0.0+exp` as ranges. Signed-off-by: Benoit Tigeot --- pkg/repo/v1/index.go | 9 ++++++++- pkg/repo/v1/index_test.go | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pkg/repo/v1/index.go b/pkg/repo/v1/index.go index 1ed7c32b6..f9829ec7f 100644 --- a/pkg/repo/v1/index.go +++ b/pkg/repo/v1/index.go @@ -178,7 +178,14 @@ func (i IndexFile) SortEntries() { // isVersionRange checks if the version string is a range constraint (e.g., "^1", "~1.10") // rather than an exact version (e.g., "1.10.0"). func isVersionRange(version string) bool { - return strings.ContainsAny(version, "^~<>=!*xX") || strings.Contains(version, "||") || strings.Contains(version, " - ") + if strings.ContainsAny(version, "^~<>=!*") || strings.Contains(version, "||") || strings.Contains(version, " - ") { + return true + } + core := version + if idx := strings.IndexAny(version, "-+"); idx != -1 { + core = version[:idx] + } + return strings.ContainsAny(core, "xX") } // Get returns the ChartVersion for the given name. diff --git a/pkg/repo/v1/index_test.go b/pkg/repo/v1/index_test.go index 7a3120c89..a86efe1e3 100644 --- a/pkg/repo/v1/index_test.go +++ b/pkg/repo/v1/index_test.go @@ -745,6 +745,10 @@ func TestIsVersionRange(t *testing.T) { {"1.0.0 - 2.0.0", true}, {"^1.0.0 || ^2.0.0", true}, {">=1.0.0 <2.0.0", true}, + // Exact versions with 'x'/'X' in prerelease or build metadata + {"1.0.0-fix", false}, + {"2.0.0-next", false}, + {"1.0.0+exp", false}, } for _, tt := range tests { From a4a9cc7a314d98456a2f23798a78e9ad05d96d0c Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Sat, 25 Apr 2026 15:13:25 +0100 Subject: [PATCH 285/571] Upgrade Go to 1.26, Kubernetes to 1.36, kstatus to 1.1 Signed-off-by: Matheus Pimenta --- .github/env | 2 +- go.mod | 35 +++++---- go.sum | 71 +++++++++---------- internal/chart/v3/lint/rules/template.go | 4 +- pkg/chart/v2/lint/rules/template.go | 4 +- pkg/cmd/lint_test.go | 2 +- .../lint-chart-with-deprecated-api-strict.txt | 2 +- .../output/lint-chart-with-deprecated-api.txt | 2 +- .../templates/horizontalpodautoscaler.yaml | 9 --- .../templates/poddisruptionbudget.yaml | 9 +++ pkg/kube/client_test.go | 1 + 11 files changed, 69 insertions(+), 72 deletions(-) delete mode 100644 pkg/cmd/testdata/testcharts/chart-with-deprecated-api/templates/horizontalpodautoscaler.yaml create mode 100644 pkg/cmd/testdata/testcharts/chart-with-deprecated-api/templates/poddisruptionbudget.yaml diff --git a/.github/env b/.github/env index fc6f21880..43ebdd36e 100644 --- a/.github/env +++ b/.github/env @@ -1,2 +1,2 @@ -GOLANG_VERSION=1.25 +GOLANG_VERSION=1.26 GOLANGCI_LINT_VERSION=v2.11.3 diff --git a/go.mod b/go.mod index 7e734a01e..bcddd6986 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module helm.sh/helm/v4 -go 1.25.0 +go 1.26.0 require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 @@ -17,7 +17,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.19.0 - github.com/fluxcd/cli-utils v1.0.0 + github.com/fluxcd/cli-utils v1.1.0 github.com/foxcpp/go-mockdns v1.2.0 github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.13.0 @@ -39,14 +39,14 @@ require ( golang.org/x/term v0.42.0 golang.org/x/text v0.36.0 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.35.4 - k8s.io/apiextensions-apiserver v0.35.4 - k8s.io/apimachinery v0.35.4 - k8s.io/apiserver v0.35.4 - k8s.io/cli-runtime v0.35.4 - k8s.io/client-go v0.35.4 - k8s.io/klog/v2 v2.130.1 - k8s.io/kubectl v0.35.4 + k8s.io/api v0.36.0 + k8s.io/apiextensions-apiserver v0.36.0 + k8s.io/apimachinery v0.36.0 + k8s.io/apiserver v0.36.0 + k8s.io/cli-runtime v0.36.0 + k8s.io/client-go v0.36.0 + k8s.io/klog/v2 v2.140.0 + k8s.io/kubectl v0.36.0 oras.land/oras-go/v2 v2.6.0 sigs.k8s.io/controller-runtime v0.23.3 sigs.k8s.io/kustomize/kyaml v0.21.1 @@ -65,7 +65,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.6.3 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/coreos/go-systemd/v22 v22.7.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect @@ -74,7 +74,7 @@ require ( github.com/docker/go-events v0.0.0-20250808211157-605354379745 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect - github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect @@ -91,7 +91,6 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect @@ -168,15 +167,15 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/grpc v1.80.0 // indirect - google.golang.org/protobuf v1.36.11 // indirect + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/component-base v0.35.4 // indirect - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + k8s.io/component-base v0.36.0 // indirect + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.21.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect ) diff --git a/go.sum b/go.sum index 9614bfb90..9b7f40f94 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,8 @@ github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNS github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= -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/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= +github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -81,8 +81,8 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a h1:UwSIFv5g5lIvbGgtf3tVwC7Ky9rmMFBp0RMs+6f6YqE= github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q= -github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= @@ -93,8 +93,8 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/cli-utils v1.0.0 h1:+luz8igR6dM5f7uHwkkMTECsl+jp0kR69POuV5aOoDs= -github.com/fluxcd/cli-utils v1.0.0/go.mod h1:ANTIXWLLsNmn5bMNxbyoY22rtwRSR/fbu+IFy756fs0= +github.com/fluxcd/cli-utils v1.1.0 h1:P2oULlj4aNSqjBGcWOCQS+TOS5ZSyoJMy1zYCpqsYus= +github.com/fluxcd/cli-utils v1.1.0/go.mod h1:+ipwad8nfETe+VB3SMgrDv6m0mqA/KQSj2wyn8Y7vmo= github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -128,7 +128,6 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -155,8 +154,6 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= @@ -373,8 +370,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -472,8 +469,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -488,28 +485,28 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.35.4 h1:P7nFYKl5vo9AGUp1Z+Pmd3p2tA7bX2wbFWCvDeRv988= -k8s.io/api v0.35.4/go.mod h1:yl4lqySWOgYJJf9RERXKUwE9g2y+CkuwG+xmcOK8wXU= -k8s.io/apiextensions-apiserver v0.35.4 h1:HeP+Upp7ItdvnyGmub0yoix+2z5+ev4M5cE5TCgtOUU= -k8s.io/apiextensions-apiserver v0.35.4/go.mod h1:ogQlk+stIE8mnoRthSYCwlOS12fVqgWFiErMwPaXA7c= -k8s.io/apimachinery v0.35.4 h1:xtdom9RG7e+yDp71uoXoJDWEE2eOiHgeO4GdBzwWpds= -k8s.io/apimachinery v0.35.4/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc= -k8s.io/apiserver v0.35.4 h1:vtuFqNFmF9bPRdHDL2lpK6qCTPWDreZJL4LRPwVM6ho= -k8s.io/apiserver v0.35.4/go.mod h1:JnBcb+J8kFXKpZkgcbcUnPBBHi4qgBii1I7dLxFY/oo= -k8s.io/cli-runtime v0.35.4 h1:8QRCXSDvopflFNM65Vkkdv42BljPdRSiqf6HFyI1iik= -k8s.io/cli-runtime v0.35.4/go.mod h1:MKLFuZxiJpm87UxjVeQRNy3sCaczHrSOPKN9pinlrM0= -k8s.io/client-go v0.35.4 h1:DN6fyaGuzK64UvnKO5fOA6ymSjvfGAnCAHAR0C66kD8= -k8s.io/client-go v0.35.4/go.mod h1:2Pg9WpsS4NeOpoYTfHHfMxBG8zFMSAUi4O/qoiJC3nY= -k8s.io/component-base v0.35.4 h1:6n1tNJ87johN0Hif0Fs8K2GMthsaUwMqCebUDLYyv7U= -k8s.io/component-base v0.35.4/go.mod h1:qaDJgz5c1KYKla9occFmlJEfPpkuA55s90G509R+PeY= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/kubectl v0.35.4 h1:IHitney6OUeH29rBQnt6Cas6az8HpFeSAohormITNMc= -k8s.io/kubectl v0.35.4/go.mod h1:CGWAaof9ae4vGDAyhnSf1bSQN/U7jiWQHLVbMbLMjRI= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80= +k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34= +k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0= +k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug= +k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ= +k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc= +k8s.io/apiserver v0.36.0 h1:Jg5OFAENUACByUCg15CmhZAYrr5ZyJ+jodyA1mHl3YE= +k8s.io/apiserver v0.36.0/go.mod h1:mHvwdHf+qKEm+1/hYm756SV+oREOKSPnsjagOpx6Vho= +k8s.io/cli-runtime v0.36.0 h1:HNxciQpQMMOKS0/GiUXcKDyA6J2FDILJj9NmP2BZrTg= +k8s.io/cli-runtime v0.36.0/go.mod h1:KObkknK9Ro5LYX+1RdiKc7C8CvGg4aX+V/Zv+E8WPHA= +k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c= +k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y= +k8s.io/component-base v0.36.0 h1:hFjEktssxiJhrK1zfybkH4kJOi8iZuF+mIDCqS5+jRo= +k8s.io/component-base v0.36.0/go.mod h1:JZvIfcNHk+uck+8LhJzhSBtydWXaZNQwX2OdL+Mnwsk= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/kubectl v0.36.0 h1:hEGr8NvIm2Wjqs2Xy48Uzmvo6lpHdGKlLyMvau2gTms= +k8s.io/kubectl v0.36.0/go.mod h1:iDe8aV5BEi45W8k+5n71I2pJ/nwE0PHDu+/2cejzYoo= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= @@ -522,7 +519,7 @@ sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7 sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/internal/chart/v3/lint/rules/template.go b/internal/chart/v3/lint/rules/template.go index 35e4940ab..a8ae910eb 100644 --- a/internal/chart/v3/lint/rules/template.go +++ b/internal/chart/v3/lint/rules/template.go @@ -28,8 +28,8 @@ import ( "slices" "strings" + "k8s.io/apimachinery/pkg/api/validate/content" "k8s.io/apimachinery/pkg/api/validation" - apipath "k8s.io/apimachinery/pkg/api/validation/path" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/yaml" @@ -292,7 +292,7 @@ func validateMetadataNameFunc(obj *k8sYamlStruct) validation.ValidateNameFunc { case "role", "clusterrole", "rolebinding", "clusterrolebinding": // https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/rbac/validation/validation.go#L32-L34 return func(name string, _ bool) []string { - return apipath.IsValidPathSegmentName(name) + return content.IsPathSegmentName(name) } default: return validation.NameIsDNSSubdomain diff --git a/pkg/chart/v2/lint/rules/template.go b/pkg/chart/v2/lint/rules/template.go index 43665aa3a..94210dec8 100644 --- a/pkg/chart/v2/lint/rules/template.go +++ b/pkg/chart/v2/lint/rules/template.go @@ -28,8 +28,8 @@ import ( "slices" "strings" + "k8s.io/apimachinery/pkg/api/validate/content" "k8s.io/apimachinery/pkg/api/validation" - apipath "k8s.io/apimachinery/pkg/api/validation/path" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/yaml" @@ -323,7 +323,7 @@ func validateMetadataNameFunc(obj *k8sYamlStruct) validation.ValidateNameFunc { case "role", "clusterrole", "rolebinding", "clusterrolebinding": // https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/rbac/validation/validation.go#L32-L34 return func(name string, _ bool) []string { - return apipath.IsValidPathSegmentName(name) + return content.IsPathSegmentName(name) } default: return validation.NameIsDNSSubdomain diff --git a/pkg/cmd/lint_test.go b/pkg/cmd/lint_test.go index a13ec423b..82fe249f6 100644 --- a/pkg/cmd/lint_test.go +++ b/pkg/cmd/lint_test.go @@ -84,7 +84,7 @@ func TestLintCmdWithKubeVersionFlag(t *testing.T) { wantError: false, }, { name: "lint chart with deprecated api version with older kube version", - cmd: "lint --kube-version 1.21.0 --strict " + testChart, + cmd: "lint --kube-version 1.20.0 --strict " + testChart, golden: "output/lint-chart-with-deprecated-api-old-k8s.txt", wantError: false, }} diff --git a/pkg/cmd/testdata/output/lint-chart-with-deprecated-api-strict.txt b/pkg/cmd/testdata/output/lint-chart-with-deprecated-api-strict.txt index a1ec4394e..c25efc1df 100644 --- a/pkg/cmd/testdata/output/lint-chart-with-deprecated-api-strict.txt +++ b/pkg/cmd/testdata/output/lint-chart-with-deprecated-api-strict.txt @@ -1,5 +1,5 @@ ==> Linting testdata/testcharts/chart-with-deprecated-api [INFO] Chart.yaml: icon is recommended -[WARNING] templates/horizontalpodautoscaler.yaml: autoscaling/v2beta1 HorizontalPodAutoscaler is deprecated in v1.22+, unavailable in v1.25+; use autoscaling/v2 HorizontalPodAutoscaler +[WARNING] templates/poddisruptionbudget.yaml: policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+; use policy/v1 PodDisruptionBudget Error: 1 chart(s) linted, 1 chart(s) failed diff --git a/pkg/cmd/testdata/output/lint-chart-with-deprecated-api.txt b/pkg/cmd/testdata/output/lint-chart-with-deprecated-api.txt index dac54620c..08dbde95d 100644 --- a/pkg/cmd/testdata/output/lint-chart-with-deprecated-api.txt +++ b/pkg/cmd/testdata/output/lint-chart-with-deprecated-api.txt @@ -1,5 +1,5 @@ ==> Linting testdata/testcharts/chart-with-deprecated-api [INFO] Chart.yaml: icon is recommended -[WARNING] templates/horizontalpodautoscaler.yaml: autoscaling/v2beta1 HorizontalPodAutoscaler is deprecated in v1.22+, unavailable in v1.25+; use autoscaling/v2 HorizontalPodAutoscaler +[WARNING] templates/poddisruptionbudget.yaml: policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+; use policy/v1 PodDisruptionBudget 1 chart(s) linted, 0 chart(s) failed diff --git a/pkg/cmd/testdata/testcharts/chart-with-deprecated-api/templates/horizontalpodautoscaler.yaml b/pkg/cmd/testdata/testcharts/chart-with-deprecated-api/templates/horizontalpodautoscaler.yaml deleted file mode 100644 index b77a4beeb..000000000 --- a/pkg/cmd/testdata/testcharts/chart-with-deprecated-api/templates/horizontalpodautoscaler.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: autoscaling/v2beta1 -kind: HorizontalPodAutoscaler -metadata: - name: deprecated -spec: - scaleTargetRef: - kind: Pod - name: pod - maxReplicas: 3 \ No newline at end of file diff --git a/pkg/cmd/testdata/testcharts/chart-with-deprecated-api/templates/poddisruptionbudget.yaml b/pkg/cmd/testdata/testcharts/chart-with-deprecated-api/templates/poddisruptionbudget.yaml new file mode 100644 index 000000000..214d3cb68 --- /dev/null +++ b/pkg/cmd/testdata/testcharts/chart-with-deprecated-api/templates/poddisruptionbudget.yaml @@ -0,0 +1,9 @@ +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: deprecated +spec: + maxUnavailable: 1 + selector: + matchLabels: + app: deprecated diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index e98d87520..ed871c05a 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -972,6 +972,7 @@ func TestGetPodList(t *testing.T) { podList, err := c.GetPodList(namespace, metav1.ListOptions{}) clientAssertions := assert.New(t) clientAssertions.NoError(err) + podList.ResourceVersion = "" clientAssertions.Equal(&responsePodList, podList) } From 4e24ee41a436889a2542a008eaa0ecab8332a1eb Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sun, 26 Apr 2026 09:16:27 -0600 Subject: [PATCH 286/571] fix(kube): prevent spurious early exit in WaitForDelete during informer sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During informer initialization there is a brief window where watched resources appear as Unknown before their real statuses are delivered. The statusObserver skips Unknown resources when waiting for deletion (they may have been deleted before the watch started), but if *all* resources are in that transient Unknown state the skipped-resource list is empty. AggregateStatus on an empty slice returns the desired status, causing cancel() to be called immediately — before any real status event has arrived. Guard against this by tracking the count of Unknown-skipped resources. When every resource was Unknown-skipped and none have a definitive status yet, defer the early-cancel decision until at least one resource reports a real status. This preserves the correct behaviour for resources that were genuinely deleted before the watch started (they eventually receive a NotFound or stay Unknown, and the aggregate succeeds), while fixing the race for resources that are transiently Unknown at startup. Also tighten the ctx.Err() check in waitForDelete: only append a deadline error when there are resource-specific errors to accompany it. A timeout while all resources are Unknown or NotFound is not itself an error — the resources are in an acceptable state for a delete wait. Fixes: TestStatusWaitForDelete/error_when_not_all_objects_are_deleted Signed-off-by: Terry Howe --- pkg/kube/statuswait.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index 59c1218ff..a93d28fc1 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -160,7 +160,10 @@ func (w *statusWaiter) waitForDelete(ctx context.Context, resourceList ResourceL errs = append(errs, fmt.Errorf("resource %s/%s/%s still exists. status: %s, message: %s", rs.Identifier.GroupKind.Kind, rs.Identifier.Namespace, rs.Identifier.Name, rs.Status, rs.Message)) } - if err := ctx.Err(); err != nil { + // Only include a deadline error when there are also resource-specific errors. + // If all resources are Unknown or NotFound (e.g. deleted before the watch started), + // a timeout is not itself an error for WaitForDelete. + if err := ctx.Err(); err != nil && len(errs) > 0 { errs = append(errs, err) } if len(errs) > 0 { @@ -234,6 +237,7 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl return func(statusCollector *collector.ResourceStatusCollector, _ event.Event) { var rss []*event.ResourceStatus var nonDesiredResources []*event.ResourceStatus + var unknownSkipped int for _, rs := range statusCollector.ResourceStatuses { if rs == nil { continue @@ -241,6 +245,7 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl // If a resource is already deleted before waiting has started, it will show as unknown. // This check ensures we don't wait forever for a resource that is already deleted. if rs.Status == status.UnknownStatus && desired == status.NotFoundStatus { + unknownSkipped++ continue } // Failed is a terminal state. This check ensures we don't wait forever for a resource @@ -254,6 +259,14 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl } } + // During informer initialization there is a brief window where existing resources + // appear as Unknown before their real status is delivered. If every resource was + // skipped as Unknown, we cannot yet distinguish "all deleted" from "not yet synced", + // so hold off on the early-cancel to avoid a spurious success or premature exit. + if unknownSkipped > 0 && len(rss) == 0 { + return + } + if aggregator.AggregateStatus(rss, desired) == desired { logger.Debug("all resources achieved desired status", "desiredStatus", desired, "resourceCount", len(rss)) cancel() From 5e09ee78ee5861b857a3526a2fd1ddf2f73950e2 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sun, 26 Apr 2026 09:49:20 -0600 Subject: [PATCH 287/571] fix(kube): always propagate context.Canceled in WaitForDelete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous change suppressed ctx.Err() whenever there were no resource-specific errors, which incorrectly swallowed context.Canceled and other non-deadline errors signalling an external interruption. Refine the condition: only suppress context.DeadlineExceeded when there are no resource-specific errors (resources are Unknown/NotFound, meaning the delete wait succeeded or resources were already gone). Any other context error — including context.Canceled — is always propagated. Signed-off-by: Terry Howe --- pkg/kube/statuswait.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index a93d28fc1..10327358e 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -160,11 +160,16 @@ func (w *statusWaiter) waitForDelete(ctx context.Context, resourceList ResourceL errs = append(errs, fmt.Errorf("resource %s/%s/%s still exists. status: %s, message: %s", rs.Identifier.GroupKind.Kind, rs.Identifier.Namespace, rs.Identifier.Name, rs.Status, rs.Message)) } - // Only include a deadline error when there are also resource-specific errors. - // If all resources are Unknown or NotFound (e.g. deleted before the watch started), - // a timeout is not itself an error for WaitForDelete. - if err := ctx.Err(); err != nil && len(errs) > 0 { - errs = append(errs, err) + if err := ctx.Err(); err != nil { + // context.Canceled and other non-deadline errors always propagate: they signal an + // external interruption regardless of resource state. + // context.DeadlineExceeded is only added when there are resource-specific errors; + // if all resources are Unknown or NotFound the timeout is not itself a failure for + // a delete wait (e.g. resources deleted before the watch started stay Unknown in + // the fake client but are effectively gone). + if !errors.Is(err, context.DeadlineExceeded) || len(errs) > 0 { + errs = append(errs, err) + } } if len(errs) > 0 { return errors.Join(errs...) From f8ba28bb17f9254bc2c1b6409b24c16a59934a9c Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 27 Apr 2026 19:48:55 +0200 Subject: [PATCH 288/571] testifylint: enable error-is-as and error-nil rules Signed-off-by: Matthieu MOREL --- .golangci.yml | 2 - internal/chart/v3/chart_test.go | 2 +- internal/plugin/runtime_extismv1_test.go | 2 +- pkg/action/install_test.go | 8 +-- pkg/action/registry_login_test.go | 10 ++-- pkg/action/upgrade_test.go | 2 +- pkg/chart/v2/chart_test.go | 2 +- pkg/cmd/helpers_test.go | 4 +- pkg/engine/engine_test.go | 5 +- pkg/registry/client_http_test.go | 11 ++-- pkg/registry/client_insecure_tls_test.go | 8 +-- pkg/registry/client_tls_test.go | 16 +++--- pkg/registry/registry_test.go | 72 ++++++++++++------------ pkg/repo/v1/repotest/tlsconfig.go | 3 +- 14 files changed, 72 insertions(+), 75 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 1ed3353b4..853324c6e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -101,8 +101,6 @@ linters: - empty - encoded-compare - equal-values - - error-is-as - - error-nil - expected-actual - float-compare - go-require diff --git a/internal/chart/v3/chart_test.go b/internal/chart/v3/chart_test.go index 5f6ca548d..f89030095 100644 --- a/internal/chart/v3/chart_test.go +++ b/internal/chart/v3/chart_test.go @@ -104,7 +104,7 @@ func TestMetadata(t *testing.T) { is.Equal("foo.yaml", chrt.Name()) is.Equal("1.0.0", chrt.AppVersion()) - is.Equal(nil, chrt.Validate()) + is.NoError(chrt.Validate()) } func TestIsRoot(t *testing.T) { diff --git a/internal/plugin/runtime_extismv1_test.go b/internal/plugin/runtime_extismv1_test.go index 8d9c55195..bcd791905 100644 --- a/internal/plugin/runtime_extismv1_test.go +++ b/internal/plugin/runtime_extismv1_test.go @@ -79,7 +79,7 @@ func TestRuntimeExtismV1InvokePlugin(t *testing.T) { Name: "Phippy", }, }) - require.Nil(t, err) + require.NoError(t, err) msg := output.Message.(schema.OutputMessageTestV1) assert.Equal(t, "Hello, Phippy! (6)", msg.Greeting) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 05ca9a75e..38d692a0b 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -867,7 +867,7 @@ func TestInstallReleaseOutputDir(t *testing.T) { test.AssertGoldenFile(t, filepath.Join(dir, "hello/templates/rbac"), "rbac.txt") _, err = os.Stat(filepath.Join(dir, "hello/templates/empty")) - is.True(errors.Is(err, fs.ErrNotExist)) + is.ErrorIs(err, fs.ErrNotExist) } func TestInstallOutputDirWithReleaseName(t *testing.T) { @@ -903,7 +903,7 @@ func TestInstallOutputDirWithReleaseName(t *testing.T) { test.AssertGoldenFile(t, filepath.Join(newDir, "hello/templates/rbac"), "rbac.txt") _, err = os.Stat(filepath.Join(newDir, "hello/templates/empty")) - is.True(errors.Is(err, fs.ErrNotExist)) + is.ErrorIs(err, fs.ErrNotExist) } func TestNameAndChart(t *testing.T) { @@ -1168,7 +1168,7 @@ func TestInstallCRDs_AlreadyExist(t *testing.T) { mockChart := buildChart(withFile(mockFile)) crdsToInstall := mockChart.CRDObjects() - assert.Nil(t, instAction.installCRDs(crdsToInstall)) + assert.NoError(t, instAction.installCRDs(crdsToInstall)) } func TestInstallCRDs_KubeClient_BuildError(t *testing.T) { @@ -1227,7 +1227,7 @@ func TestCheckDependencies(t *testing.T) { dependency := chart.Dependency{Name: "hello"} mockChart := buildChart(withDependency()) - assert.Nil(t, CheckDependencies(mockChart, []ci.Dependency{&dependency})) + assert.NoError(t, CheckDependencies(mockChart, []ci.Dependency{&dependency})) } func TestCheckDependencies_MissingDependency(t *testing.T) { diff --git a/pkg/action/registry_login_test.go b/pkg/action/registry_login_test.go index 590673b3a..474118c9f 100644 --- a/pkg/action/registry_login_test.go +++ b/pkg/action/registry_login_test.go @@ -37,7 +37,7 @@ func TestWithCertFile(t *testing.T) { certFile := "testdata/cert.pem" opt := WithCertFile(certFile) - assert.Nil(t, opt(client)) + assert.NoError(t, opt(client)) assert.Equal(t, certFile, client.certFile) } @@ -47,7 +47,7 @@ func TestWithInsecure(t *testing.T) { opt := WithInsecure(true) - assert.Nil(t, opt(client)) + assert.NoError(t, opt(client)) assert.True(t, client.insecure) } @@ -58,7 +58,7 @@ func TestWithKeyFile(t *testing.T) { keyFile := "testdata/key.pem" opt := WithKeyFile(keyFile) - assert.Nil(t, opt(client)) + assert.NoError(t, opt(client)) assert.Equal(t, keyFile, client.keyFile) } @@ -69,7 +69,7 @@ func TestWithCAFile(t *testing.T) { caFile := "testdata/ca.pem" opt := WithCAFile(caFile) - assert.Nil(t, opt(client)) + assert.NoError(t, opt(client)) assert.Equal(t, caFile, client.caFile) } @@ -79,6 +79,6 @@ func TestWithPlainHTTPLogin(t *testing.T) { opt := WithPlainHTTPLogin(true) - assert.Nil(t, opt(client)) + assert.NoError(t, opt(client)) assert.True(t, client.plainHTTP) } diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 393692976..4e48b28a6 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -716,7 +716,7 @@ func TestGetUpgradeServerSideValue(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { serverSideApply, err := getUpgradeServerSideValue(tt.actionServerSideOption, tt.releaseApplyMethod) - assert.Nil(t, err) + assert.NoError(t, err) assert.Equal(t, tt.expectedServerSideApply, serverSideApply) }) } diff --git a/pkg/chart/v2/chart_test.go b/pkg/chart/v2/chart_test.go index d44e7251b..52e9c6b43 100644 --- a/pkg/chart/v2/chart_test.go +++ b/pkg/chart/v2/chart_test.go @@ -104,7 +104,7 @@ func TestMetadata(t *testing.T) { is.Equal("foo.yaml", chrt.Name()) is.Equal("1.0.0", chrt.AppVersion()) - is.Equal(nil, chrt.Validate()) + is.NoError(chrt.Validate()) } func TestIsRoot(t *testing.T) { diff --git a/pkg/cmd/helpers_test.go b/pkg/cmd/helpers_test.go index 08065499e..9b4483793 100644 --- a/pkg/cmd/helpers_test.go +++ b/pkg/cmd/helpers_test.go @@ -290,14 +290,14 @@ func TestCmdGetDryRunFlagStrategy(t *testing.T) { if tc.ExpectedError { assert.Error(t, err) } else { - assert.Nil(t, err) + assert.NoError(t, err) assert.Equal(t, tc.ExpectedStrategy, dryRunStrategy) } if tc.ExpectedLog != nil { logResult := map[string]string{} err = json.Unmarshal(logBuf.Bytes(), &logResult) - require.Nil(t, err) + require.NoError(t, err) assert.Equal(t, tc.ExpectedLog.Level, logResult["level"]) assert.Equal(t, tc.ExpectedLog.Msg, logResult["msg"]) diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 869b5d202..f618dbb44 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -27,6 +27,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -1357,7 +1358,7 @@ NestedHelperFunctions/charts/common/templates/_helpers_2.tpl:1:49 } _, err := Render(c, vals) - assert.NotNil(t, err) + require.Error(t, err) assert.Equal(t, expectedErrorMessage, err.Error()) } @@ -1391,7 +1392,7 @@ template: no template "nested_helper.name" associated with template "gotpl"` } _, err := Render(c, vals) - assert.NotNil(t, err) + require.Error(t, err) assert.Equal(t, expectedErrorMessage, err.Error()) } diff --git a/pkg/registry/client_http_test.go b/pkg/registry/client_http_test.go index 1c6751559..3eb74c541 100644 --- a/pkg/registry/client_http_test.go +++ b/pkg/registry/client_http_test.go @@ -17,7 +17,6 @@ limitations under the License. package registry import ( - "errors" "os" "testing" @@ -43,12 +42,12 @@ func (suite *HTTPRegistryClientTestSuite) Test_0_Login() { err := suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth("badverybad", "ohsobad"), LoginOptPlainText(true)) - suite.NotNil(err, "error logging into registry with bad credentials") + suite.Require().Error(err, "error logging into registry with bad credentials") err = suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth(testUsername, testPassword), LoginOptPlainText(true)) - suite.Nil(err, "no error logging into registry with good credentials") + suite.Require().NoError(err, "no error logging into registry with good credentials") } func (suite *HTTPRegistryClientTestSuite) Test_1_Push() { @@ -68,15 +67,15 @@ func (suite *HTTPRegistryClientTestSuite) Test_4_ManInTheMiddle() { // returns content that does not match the expected digest _, err := suite.RegistryClient.Pull(ref) - suite.NotNil(err) - suite.True(errors.Is(err, content.ErrMismatchedDigest)) + suite.Require().Error(err) + suite.ErrorIs(err, content.ErrMismatchedDigest) } func (suite *HTTPRegistryClientTestSuite) Test_5_ImageIndex() { ref := suite.FakeRegistryHost + "/testrepo/image-index:0.1.0" _, err := suite.RegistryClient.Pull(ref) - suite.Nil(err) + suite.Require().NoError(err) } func TestHTTPRegistryClientTestSuite(t *testing.T) { diff --git a/pkg/registry/client_insecure_tls_test.go b/pkg/registry/client_insecure_tls_test.go index 2774f5e6f..8c4e928e4 100644 --- a/pkg/registry/client_insecure_tls_test.go +++ b/pkg/registry/client_insecure_tls_test.go @@ -41,12 +41,12 @@ func (suite *InsecureTLSRegistryClientTestSuite) Test_0_Login() { err := suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth("badverybad", "ohsobad"), LoginOptInsecure(true)) - suite.NotNil(err, "error logging into registry with bad credentials") + suite.Require().Error(err, "error logging into registry with bad credentials") err = suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth(testUsername, testPassword), LoginOptInsecure(true)) - suite.Nil(err, "no error logging into registry with good credentials") + suite.Require().NoError(err, "no error logging into registry with good credentials") } func (suite *InsecureTLSRegistryClientTestSuite) Test_1_Push() { @@ -65,11 +65,11 @@ func (suite *InsecureTLSRegistryClientTestSuite) Test_4_Logout() { err := suite.RegistryClient.Logout("this-host-aint-real:5000") if err != nil { // credential backend for mac generates an error - suite.NotNil(err, "failed to delete the credential for this-host-aint-real:5000") + suite.Require().Error(err, "failed to delete the credential for this-host-aint-real:5000") } err = suite.RegistryClient.Logout(suite.DockerRegistryHost) - suite.Nil(err, "no error logging out of registry") + suite.Require().NoError(err, "no error logging out of registry") } func TestInsecureTLSRegistryClientTestSuite(t *testing.T) { diff --git a/pkg/registry/client_tls_test.go b/pkg/registry/client_tls_test.go index ddeeb3b66..2bea0377f 100644 --- a/pkg/registry/client_tls_test.go +++ b/pkg/registry/client_tls_test.go @@ -43,26 +43,26 @@ func (suite *TLSRegistryClientTestSuite) Test_0_Login() { err := suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth("badverybad", "ohsobad"), LoginOptTLSClientConfig(tlsCert, tlsKey, tlsCA)) - suite.NotNil(err, "error logging into registry with bad credentials") + suite.Require().Error(err, "error logging into registry with bad credentials") err = suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth(testUsername, testPassword), LoginOptTLSClientConfig(tlsCert, tlsKey, tlsCA)) - suite.Nil(err, "no error logging into registry with good credentials") + suite.Require().NoError(err, "no error logging into registry with good credentials") } func (suite *TLSRegistryClientTestSuite) Test_1_Login() { err := suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth("badverybad", "ohsobad"), LoginOptTLSClientConfigFromConfig(&tls.Config{})) - suite.NotNil(err, "error logging into registry with bad credentials") + suite.Require().Error(err, "error logging into registry with bad credentials") // Create a *tls.Config from tlsCert, tlsKey, and tlsCA. cert, err := tls.LoadX509KeyPair(tlsCert, tlsKey) - suite.Nil(err, "error loading x509 key pair") + suite.Require().NoError(err, "error loading x509 key pair") rootCAs := x509.NewCertPool() caCert, err := os.ReadFile(tlsCA) - suite.Nil(err, "error reading CA certificate") + suite.Require().NoError(err, "error reading CA certificate") rootCAs.AppendCertsFromPEM(caCert) conf := &tls.Config{ Certificates: []tls.Certificate{cert}, @@ -72,7 +72,7 @@ func (suite *TLSRegistryClientTestSuite) Test_1_Login() { err = suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth(testUsername, testPassword), LoginOptTLSClientConfigFromConfig(conf)) - suite.Nil(err, "no error logging into registry with good credentials") + suite.Require().NoError(err, "no error logging into registry with good credentials") } func (suite *TLSRegistryClientTestSuite) Test_1_Push() { @@ -91,11 +91,11 @@ func (suite *TLSRegistryClientTestSuite) Test_4_Logout() { err := suite.RegistryClient.Logout("this-host-aint-real:5000") if err != nil { // credential backend for mac generates an error - suite.NotNil(err, "failed to delete the credential for this-host-aint-real:5000") + suite.Require().Error(err, "failed to delete the credential for this-host-aint-real:5000") } err = suite.RegistryClient.Logout(suite.DockerRegistryHost) - suite.Nil(err, "no error logging out of registry") + suite.Require().NoError(err, "no error logging out of registry") } func TestTLSRegistryClientTestSuite(t *testing.T) { diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index b0c08fa3c..9b9f51ca0 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -107,26 +107,26 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool) { TLSClientConfig: tlsConf, }, } - suite.Nil(err, "no error loading tls config") + suite.Require().NoError(err, "no error loading tls config") opts = append(opts, ClientOptHTTPClient(httpClient)) } else { opts = append(opts, ClientOptPlainHTTP()) } suite.RegistryClient, err = NewClient(opts...) - suite.Nil(err, "no error creating registry client") + suite.Require().NoError(err, "no error creating registry client") // create htpasswd file (w BCrypt, which is required) pwBytes, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost) - suite.Nil(err, "no error generating bcrypt password for test htpasswd file") + suite.Require().NoError(err, "no error generating bcrypt password for test htpasswd file") htpasswdPath := filepath.Join(suite.WorkspaceDir, testHtpasswdFileBasename) err = os.WriteFile(htpasswdPath, fmt.Appendf(nil, "%s:%s\n", testUsername, string(pwBytes)), 0644) - suite.Nil(err, "no error creating test htpasswd file") + suite.Require().NoError(err, "no error creating test htpasswd file") // Registry config config := &configuration.Configuration{} ln, err := net.Listen("tcp", "127.0.0.1:0") - suite.Nil(err, "no error finding free port for test registry") + suite.Require().NoError(err, "no error finding free port for test registry") defer func() { _ = ln.Close() }() // Change the registry host to another host which is not localhost. @@ -159,7 +159,7 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool) { } } suite.dockerRegistry, err = registry.NewRegistry(context.Background(), config) - suite.Nil(err, "no error creating test registry") + suite.Require().NoError(err, "no error creating test registry") suite.FakeRegistryHost = initFakeRegistryTestServer() suite.CompromisedRegistryHost = initCompromisedRegistryTestServer() @@ -386,61 +386,61 @@ func testPush(suite *TestRegistry) { // Bad bytes ref := suite.DockerRegistryHost + "/testrepo/testchart:1.2.3" _, err := suite.RegistryClient.Push([]byte("hello"), ref, PushOptCreationTime(testingChartCreationTime)) - suite.NotNil(err, "error pushing non-chart bytes") + suite.Require().Error(err, "error pushing non-chart bytes") // Load a test chart chartData, err := os.ReadFile("../repo/v1/repotest/testdata/examplechart-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") + suite.Require().NoError(err, "no error loading test chart") meta, err := extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") + suite.Require().NoError(err, "no error extracting chart meta") // non-strict ref (chart name) ref = fmt.Sprintf("%s/testrepo/boop:%s", suite.DockerRegistryHost, meta.Version) _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) - suite.NotNil(err, "error pushing non-strict ref (bad basename)") + suite.Require().Error(err, "error pushing non-strict ref (bad basename)") // non-strict ref (chart name), with strict mode disabled _, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false), PushOptCreationTime(testingChartCreationTime)) - suite.Nil(err, "no error pushing non-strict ref (bad basename), with strict mode disabled") + suite.Require().NoError(err, "no error pushing non-strict ref (bad basename), with strict mode disabled") // non-strict ref (chart version) ref = fmt.Sprintf("%s/testrepo/%s:latest", suite.DockerRegistryHost, meta.Name) _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) - suite.NotNil(err, "error pushing non-strict ref (bad tag)") + suite.Require().Error(err, "error pushing non-strict ref (bad tag)") // non-strict ref (chart version), with strict mode disabled _, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false), PushOptCreationTime(testingChartCreationTime)) - suite.Nil(err, "no error pushing non-strict ref (bad tag), with strict mode disabled") + suite.Require().NoError(err, "no error pushing non-strict ref (bad tag), with strict mode disabled") // basic push, good ref chartData, err = os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") + suite.Require().NoError(err, "no error loading test chart") meta, err = extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") + suite.Require().NoError(err, "no error extracting chart meta") ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) - suite.Nil(err, "no error pushing good ref") + suite.Require().NoError(err, "no error pushing good ref") _, err = suite.RegistryClient.Pull(ref) - suite.Nil(err, "no error pulling a simple chart") + suite.Require().NoError(err, "no error pulling a simple chart") // Load another test chart chartData, err = os.ReadFile("../downloader/testdata/signtest-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") + suite.Require().NoError(err, "no error loading test chart") meta, err = extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") + suite.Require().NoError(err, "no error extracting chart meta") // Load prov file provData, err := os.ReadFile("../downloader/testdata/signtest-0.1.0.tgz.prov") - suite.Nil(err, "no error loading test prov") + suite.Require().NoError(err, "no error loading test prov") // push with prov ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) result, err := suite.RegistryClient.Push(chartData, ref, PushOptProvData(provData), PushOptCreationTime(testingChartCreationTime)) - suite.Nil(err, "no error pushing good ref with prov") + suite.Require().NoError(err, "no error pushing good ref with prov") _, err = suite.RegistryClient.Pull(ref, PullOptWithProv(true)) - suite.Nil(err, "no error pulling a simple chart") + suite.Require().NoError(err, "no error pulling a simple chart") // Validate the output // Note: these digests/sizes etc may change if the test chart/prov files are modified, @@ -470,50 +470,50 @@ func testPull(suite *TestRegistry) { // bad/missing ref ref := suite.DockerRegistryHost + "/testrepo/no-existy:1.2.3" _, err := suite.RegistryClient.Pull(ref) - suite.NotNil(err, "error on bad/missing ref") + suite.Require().Error(err, "error on bad/missing ref") // Load test chart (to build ref pushed in previous test) chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") + suite.Require().NoError(err, "no error loading test chart") meta, err := extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") + suite.Require().NoError(err, "no error extracting chart meta") ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) // Simple pull, chart only _, err = suite.RegistryClient.Pull(ref) - suite.Nil(err, "no error pulling a simple chart") + suite.Require().NoError(err, "no error pulling a simple chart") // Simple pull with prov (no prov uploaded) _, err = suite.RegistryClient.Pull(ref, PullOptWithProv(true)) - suite.NotNil(err, "error pulling a chart with prov when no prov exists") + suite.Require().Error(err, "error pulling a chart with prov when no prov exists") // Simple pull with prov, ignoring missing prov _, err = suite.RegistryClient.Pull(ref, PullOptWithProv(true), PullOptIgnoreMissingProv(true)) - suite.Nil(err, + suite.Require().NoError(err, "no error pulling a chart with prov when no prov exists, ignoring missing") // Load test chart (to build ref pushed in previous test) chartData, err = os.ReadFile("../downloader/testdata/signtest-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") + suite.Require().NoError(err, "no error loading test chart") meta, err = extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") + suite.Require().NoError(err, "no error extracting chart meta") ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) // Load prov file provData, err := os.ReadFile("../downloader/testdata/signtest-0.1.0.tgz.prov") - suite.Nil(err, "no error loading test prov") + suite.Require().NoError(err, "no error loading test prov") // no chart and no prov causes error _, err = suite.RegistryClient.Pull(ref, PullOptWithChart(false), PullOptWithProv(false)) - suite.NotNil(err, "error on both no chart and no prov") + suite.Require().Error(err, "error on both no chart and no prov") // full pull with chart and prov result, err := suite.RegistryClient.Pull(ref, PullOptWithProv(true)) - suite.Require().Nil(err, "no error pulling a chart with prov") + suite.Require().NoError(err, "no error pulling a chart with prov") // Validate the output // Note: these digests/sizes etc may change if the test chart/prov files are modified, @@ -548,13 +548,13 @@ func testPull(suite *TestRegistry) { func testTags(suite *TestRegistry) { // Load test chart (to build ref pushed in previous test) chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") + suite.Require().NoError(err, "no error loading test chart") meta, err := extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") + suite.Require().NoError(err, "no error extracting chart meta") ref := fmt.Sprintf("%s/testrepo/%s", suite.DockerRegistryHost, meta.Name) // Query for tags and validate length tags, err := suite.RegistryClient.Tags(ref) - suite.Nil(err, "no error retrieving tags") + suite.Require().NoError(err, "no error retrieving tags") suite.Equal(1, len(tags)) } diff --git a/pkg/repo/v1/repotest/tlsconfig.go b/pkg/repo/v1/repotest/tlsconfig.go index 3ea7338ff..d579f8054 100644 --- a/pkg/repo/v1/repotest/tlsconfig.go +++ b/pkg/repo/v1/repotest/tlsconfig.go @@ -35,8 +35,7 @@ func MakeTestTLSConfig(t *testing.T, path string) *tls.Config { tlsutil.WithCertKeyPairFiles(pub, priv), tlsutil.WithCAFile(ca), ) - //require.Nil(t, err, err.Error()) - require.Nil(t, err) + require.NoError(t, err) tlsConf.ServerName = "helm.sh" From 277d9702555532d13426119d31c70fffb389d589 Mon Sep 17 00:00:00 2001 From: Gagan H R Date: Tue, 28 Apr 2026 22:32:21 +0530 Subject: [PATCH 289/571] fix: adds topLevel permissions to improve openSSF scores Signed-off-by: Gagan H R --- .github/workflows/codeql-analysis.yml | 7 ++++--- .github/workflows/stale.yaml | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 972602fea..51ba3ed3f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -24,14 +24,15 @@ on: schedule: - cron: '29 6 * * 6' -permissions: - contents: read - security-events: write +permissions: {} jobs: analyze: name: Analyze runs-on: ubuntu-latest + permissions: + contents: read + security-events: write strategy: fail-fast: false diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 7d41280ad..9b0c29952 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -3,9 +3,14 @@ on: schedule: - cron: "0 0 * * *" +permissions: {} + jobs: stale: runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write steps: - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 with: From 081c6dff537087f52ec6e470d8986439e24e8e33 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Thu, 30 Apr 2026 14:23:01 +0100 Subject: [PATCH 290/571] Upgrade kstatus to 1.2 and controller-runtime to 0.24 Signed-off-by: Matheus Pimenta --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index bcddd6986..182898384 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.19.0 - github.com/fluxcd/cli-utils v1.1.0 + github.com/fluxcd/cli-utils v1.2.0 github.com/foxcpp/go-mockdns v1.2.0 github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.13.0 @@ -48,7 +48,7 @@ require ( k8s.io/klog/v2 v2.140.0 k8s.io/kubectl v0.36.0 oras.land/oras-go/v2 v2.6.0 - sigs.k8s.io/controller-runtime v0.23.3 + sigs.k8s.io/controller-runtime v0.24.0 sigs.k8s.io/kustomize/kyaml v0.21.1 sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index 9b7f40f94..9d9e3fee4 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/cli-utils v1.1.0 h1:P2oULlj4aNSqjBGcWOCQS+TOS5ZSyoJMy1zYCpqsYus= -github.com/fluxcd/cli-utils v1.1.0/go.mod h1:+ipwad8nfETe+VB3SMgrDv6m0mqA/KQSj2wyn8Y7vmo= +github.com/fluxcd/cli-utils v1.2.0 h1:1o07pXTMxJ/XJ1GpAbLtjdXwfCUMq4Ku1OcnvJHLohI= +github.com/fluxcd/cli-utils v1.2.0/go.mod h1:d5HdTDdR5sCbsIbgtOQ7x7srKYwYeZORU6CD2yn4j/M= github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -509,8 +509,8 @@ k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0x k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= -sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= -sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= +sigs.k8s.io/controller-runtime v0.24.0 h1:Ck6N2LdS8Lovy1o25BB4r1xjvLEKUl1s2o9kU+KWDE4= +sigs.k8s.io/controller-runtime v0.24.0/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= From 73f71bceae2674d41fb2f082ee4fa5a842b613cc Mon Sep 17 00:00:00 2001 From: Ogulcan Aydogan Date: Thu, 30 Apr 2026 14:35:16 +0100 Subject: [PATCH 291/571] fix(registry): remove pre-Go-1.20 transport cloner fallback Helm now requires Go 1.26 (#32078); the cloner[T] type-assertion fallback in transport.go was a defensive shim for Go versions before http.Transport.Clone() existed. The fallback path is unreachable on supported Go versions. Refs: #31386 Signed-off-by: Ogulcan Aydogan --- pkg/registry/transport.go | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/pkg/registry/transport.go b/pkg/registry/transport.go index f039a8159..e4177efb3 100644 --- a/pkg/registry/transport.go +++ b/pkg/registry/transport.go @@ -52,22 +52,10 @@ type LoggingTransport struct { // NewTransport creates and returns a new instance of LoggingTransport func NewTransport(debug bool) *retry.Transport { - type cloner[T any] interface { - Clone() T - } - - // try to copy (clone) the http.DefaultTransport so any mutations we - // perform on it (e.g. TLS config) are not reflected globally - // follow https://github.com/golang/go/issues/39299 for a more elegant - // solution in the future + // clone http.DefaultTransport so mutations (e.g. TLS config) are not + // reflected globally transport := http.DefaultTransport - if t, ok := transport.(cloner[*http.Transport]); ok { - transport = t.Clone() - } else if t, ok := transport.(cloner[http.RoundTripper]); ok { - // this branch will not be used with go 1.20, it was added - // optimistically to try to clone if the http.DefaultTransport - // implementation changes, still the Clone method in that case - // might not return http.RoundTripper... + if t, ok := transport.(*http.Transport); ok { transport = t.Clone() } if debug { From e61bbfbfff41958b0ba1984e4d6799fe131f325e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 21:34:33 +0000 Subject: [PATCH 292/571] chore(deps): bump github.com/Masterminds/semver/v3 from 3.4.0 to 3.5.0 Bumps [github.com/Masterminds/semver/v3](https://github.com/Masterminds/semver) from 3.4.0 to 3.5.0. - [Release notes](https://github.com/Masterminds/semver/releases) - [Changelog](https://github.com/Masterminds/semver/blob/master/CHANGELOG.md) - [Commits](https://github.com/Masterminds/semver/compare/v3.4.0...v3.5.0) --- updated-dependencies: - dependency-name: github.com/Masterminds/semver/v3 dependency-version: 3.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 182898384..323d6eedb 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 github.com/BurntSushi/toml v1.6.0 github.com/DATA-DOG/go-sqlmock v1.5.2 - github.com/Masterminds/semver/v3 v3.4.0 + github.com/Masterminds/semver/v3 v3.5.0 github.com/Masterminds/sprig/v3 v3.3.0 github.com/Masterminds/squirrel v1.5.4 github.com/Masterminds/vcs v1.13.3 diff --git a/go.sum b/go.sum index 9d9e3fee4..d47661f15 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= From 58e8ffdc3302260b1b55718c9b72c6f169a76ee0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 00:58:20 +0000 Subject: [PATCH 293/571] chore(deps): bump github/codeql-action from 4.35.2 to 4.35.3 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.2 to 4.35.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/95e58e9a2cdfd71adc6e0353d5c52f41a045d225...e46ed2cbd01164d986452f91f178727624ae40d7) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.35.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 51ba3ed3f..6714fc8de 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # pinv4.35.2 + uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # pinv4.35.3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # pinv4.35.2 + uses: github/codeql-action/autobuild@e46ed2cbd01164d986452f91f178727624ae40d7 # pinv4.35.3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # pinv4.35.2 + uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # pinv4.35.3 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 41e2f1254..5d78ddd61 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 + uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3 with: sarif_file: results.sarif From 12f2c41c0d7a74739c58a5995cbbb3125d9247e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 01:01:06 +0000 Subject: [PATCH 294/571] chore(deps): bump github.com/distribution/distribution/v3 Bumps [github.com/distribution/distribution/v3](https://github.com/distribution/distribution) from 3.1.0 to 3.1.1. - [Release notes](https://github.com/distribution/distribution/releases) - [Commits](https://github.com/distribution/distribution/compare/v3.1.0...v3.1.1) --- updated-dependencies: - dependency-name: github.com/distribution/distribution/v3 dependency-version: 3.1.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 323d6eedb..40c02c7e1 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/ProtonMail/go-crypto v1.4.1 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/cyphar/filepath-securejoin v0.6.1 - github.com/distribution/distribution/v3 v3.1.0 + github.com/distribution/distribution/v3 v3.1.1 github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.19.0 diff --git a/go.sum b/go.sum index d47661f15..f1a2ca9f8 100644 --- a/go.sum +++ b/go.sum @@ -67,8 +67,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/distribution/distribution/v3 v3.1.0 h1:u1v788HreKTLGdNY6s7px8Exgrs9mZ9UrCDjSrpCM8g= -github.com/distribution/distribution/v3 v3.1.0/go.mod h1:73BuF5/ziMHNVt7nnL1roYpH4Eg/FgUlKZm3WryIx/o= +github.com/distribution/distribution/v3 v3.1.1 h1:KUbk7C8CfaLXy8kbf/hGq9cad/wCoLB6dbWH6DMbmX0= +github.com/distribution/distribution/v3 v3.1.1/go.mod h1:d7lXwZpph0bVcOj4Aqn0nMrWHIwRQGdiV5TLeI+/w6Y= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= From 075c096afec70155bc43ac3587a119df1ae5fcc6 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Mon, 29 Sep 2025 10:00:19 -0600 Subject: [PATCH 295/571] chore: replace mitchellh/gox with goreleaser Signed-off-by: Terry Howe --- .goreleaser.yml | 78 +++++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 11 +++---- 2 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 .goreleaser.yml diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 000000000..264a4f206 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,78 @@ +project_name: helm + +before: + hooks: + - go mod tidy + +dist: _dist +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + goarch: + - amd64 + - arm64 + - arm + - "386" + - ppc64le + - s390x + - riscv64 + goarm: + - "7" + ignore: + - goos: darwin + goarch: "386" + - goos: darwin + goarch: arm + - goos: darwin + goarch: ppc64le + - goos: darwin + goarch: s390x + - goos: darwin + goarch: riscv64 + - goos: windows + goarch: arm + - goos: windows + goarch: ppc64le + - goos: windows + goarch: s390x + - goos: windows + goarch: riscv64 + main: ./cmd/helm + binary: helm + ldflags: + - "{{ .Env.LDFLAGS }}" + flags: + - -trimpath + dir: . + +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of uname. + name_template: >- + {{ .ProjectName }}- + {{- title .Os }}- + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + # use zip for windows archives + format_overrides: + - goos: windows + format: zip + +checksum: + name_template: 'checksums.txt' + +snapshot: + name_template: "{{ incpatch .Version }}-next" + +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' \ No newline at end of file diff --git a/Makefile b/Makefile index a18b83f0d..3d4d8261e 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,8 @@ GOBIN = $(shell go env GOBIN) ifeq ($(GOBIN),) GOBIN = $(shell go env GOPATH)/bin endif -GOX = $(GOBIN)/gox +GOTOOLCHAIN = $(shell awk '/^go / {print "go" $$2}' go.mod) +GORELEASER = $(GOBIN)/goreleaser GOIMPORTS = $(GOBIN)/goimports ARCH = $(shell go env GOARCH) @@ -162,8 +163,8 @@ gen-test-golden: test-unit # dependencies to the go.mod file. To avoid that we change to a directory # without a go.mod file when downloading the following dependencies -$(GOX): - (cd /; go install github.com/mitchellh/gox@v1.0.2-0.20220701044238-9f712387e2d2) +$(GORELEASER): + (cd /; GOTOOLCHAIN=$(GOTOOLCHAIN) go install github.com/goreleaser/goreleaser@latest) $(GOIMPORTS): (cd /; go install golang.org/x/tools/cmd/goimports@latest) @@ -173,8 +174,8 @@ $(GOIMPORTS): .PHONY: build-cross build-cross: LDFLAGS += -extldflags "-static" -build-cross: $(GOX) - GOFLAGS="-trimpath" CGO_ENABLED=0 $(GOX) -parallel=3 -output="_dist/{{.OS}}-{{.Arch}}/$(BINNAME)" -osarch='$(TARGETS)' $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm +build-cross: $(GORELEASER) + LDFLAGS='$(LDFLAGS)' $(GORELEASER) build --snapshot --clean .PHONY: dist dist: From e7bea8513c30475664919f031774e18fecdf1f66 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Wed, 29 Oct 2025 13:02:15 -0600 Subject: [PATCH 296/571] remove GOTOOLCHAIN Signed-off-by: Terry Howe --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 3d4d8261e..85fae12b2 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,6 @@ GOBIN = $(shell go env GOBIN) ifeq ($(GOBIN),) GOBIN = $(shell go env GOPATH)/bin endif -GOTOOLCHAIN = $(shell awk '/^go / {print "go" $$2}' go.mod) GORELEASER = $(GOBIN)/goreleaser GOIMPORTS = $(GOBIN)/goimports ARCH = $(shell go env GOARCH) @@ -164,7 +163,7 @@ gen-test-golden: test-unit # without a go.mod file when downloading the following dependencies $(GORELEASER): - (cd /; GOTOOLCHAIN=$(GOTOOLCHAIN) go install github.com/goreleaser/goreleaser@latest) + (cd /; go install github.com/goreleaser/goreleaser@latest) $(GOIMPORTS): (cd /; go install golang.org/x/tools/cmd/goimports@latest) From e368f170af8a200e672adac5f765b8101db0c8fa Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Wed, 29 Oct 2025 13:30:27 -0600 Subject: [PATCH 297/571] update configuration to v2 Signed-off-by: Terry Howe --- .goreleaser.yml | 13 +++++++------ Makefile | 3 ++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 264a4f206..5ac434dda 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,3 +1,5 @@ +version: 2 + project_name: helm before: @@ -50,7 +52,7 @@ builds: dir: . archives: - - format: tar.gz + - id: default # this name template makes the OS and Arch compatible with the results of uname. name_template: >- {{ .ProjectName }}- @@ -59,16 +61,15 @@ archives: {{- else if eq .Arch "386" }}i386 {{- else }}{{ .Arch }}{{ end }} {{- if .Arm }}v{{ .Arm }}{{ end }} - # use zip for windows archives - format_overrides: - - goos: windows - format: zip + files: + - LICENSE + - README.md checksum: name_template: 'checksums.txt' snapshot: - name_template: "{{ incpatch .Version }}-next" + version_template: "{{ incpatch .Version }}-next" changelog: sort: asc diff --git a/Makefile b/Makefile index 85fae12b2..022320d1f 100644 --- a/Makefile +++ b/Makefile @@ -163,7 +163,8 @@ gen-test-golden: test-unit # without a go.mod file when downloading the following dependencies $(GORELEASER): - (cd /; go install github.com/goreleaser/goreleaser@latest) + echo go install github.com/goreleaser/goreleaser/v2@latest + (cd /; go install github.com/goreleaser/goreleaser/v2@latest) $(GOIMPORTS): (cd /; go install golang.org/x/tools/cmd/goimports@latest) From a9659b07e3eec20ab5b964fddae05f51f478f704 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Wed, 29 Oct 2025 13:49:24 -0600 Subject: [PATCH 298/571] fix artifact directory Signed-off-by: Terry Howe --- .goreleaser.yml => .goreleaser.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) rename .goreleaser.yml => .goreleaser.yaml (91%) diff --git a/.goreleaser.yml b/.goreleaser.yaml similarity index 91% rename from .goreleaser.yml rename to .goreleaser.yaml index 5ac434dda..a81a97845 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yaml @@ -35,6 +35,8 @@ builds: goarch: s390x - goos: darwin goarch: riscv64 + - goos: windows + goarch: "386" - goos: windows goarch: arm - goos: windows @@ -44,7 +46,8 @@ builds: - goos: windows goarch: riscv64 main: ./cmd/helm - binary: helm + no_unique_dist_dir: true + binary: "{{ .Os }}-{{ .Arch }}/helm" ldflags: - "{{ .Env.LDFLAGS }}" flags: @@ -55,7 +58,6 @@ archives: - id: default # this name template makes the OS and Arch compatible with the results of uname. name_template: >- - {{ .ProjectName }}- {{- title .Os }}- {{- if eq .Arch "amd64" }}x86_64 {{- else if eq .Arch "386" }}i386 From 45336ccd5b2621357e3f785c1fe93627c5990a6e Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Wed, 29 Oct 2025 15:02:22 -0600 Subject: [PATCH 299/571] add support for loong64 Signed-off-by: Terry Howe --- .goreleaser.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index a81a97845..f4d19a967 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -22,6 +22,7 @@ builds: - ppc64le - s390x - riscv64 + - loong64 goarm: - "7" ignore: @@ -35,6 +36,8 @@ builds: goarch: s390x - goos: darwin goarch: riscv64 + - goos: darwin + goarch: loong64 - goos: windows goarch: "386" - goos: windows @@ -45,6 +48,8 @@ builds: goarch: s390x - goos: windows goarch: riscv64 + - goos: windows + goarch: loong64 main: ./cmd/helm no_unique_dist_dir: true binary: "{{ .Os }}-{{ .Arch }}/helm" From 37284a9211972f7f41a2acc3c3313517596dd4b0 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Wed, 29 Oct 2025 15:30:10 -0600 Subject: [PATCH 300/571] fix goreleaser archive Signed-off-by: Terry Howe --- .goreleaser.yaml | 8 ++++++-- Makefile | 8 +------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index f4d19a967..e7d93d01c 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -63,11 +63,15 @@ archives: - id: default # this name template makes the OS and Arch compatible with the results of uname. name_template: >- - {{- title .Os }}- + {{- .ProjectName }}- + {{- .Version }}- + {{- .Os }}- {{- if eq .Arch "amd64" }}x86_64 {{- else if eq .Arch "386" }}i386 {{- else }}{{ .Arch }}{{ end }} - {{- if .Arm }}v{{ .Arm }}{{ end }} + formats: + - tar.gz + - zip files: - LICENSE - README.md diff --git a/Makefile b/Makefile index 022320d1f..bca7b28d8 100644 --- a/Makefile +++ b/Makefile @@ -179,13 +179,7 @@ build-cross: $(GORELEASER) .PHONY: dist dist: - ( \ - cd _dist && \ - $(DIST_DIRS) cp ../LICENSE {} \; && \ - $(DIST_DIRS) cp ../README.md {} \; && \ - $(DIST_DIRS) tar -zcf helm-${VERSION}-{}.tar.gz {} \; && \ - $(DIST_DIRS) zip -r helm-${VERSION}-{}.zip {} \; \ - ) + LDFLAGS='$(LDFLAGS)' $(GORELEASER) release --snapshot --clean .PHONY: fetch-dist fetch-dist: From 5a75279c1a017a60b97bd44986288af7399c6ff8 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Wed, 29 Oct 2025 16:03:51 -0600 Subject: [PATCH 301/571] Fix archive name Signed-off-by: Terry Howe --- .github/workflows/release.yml | 1 - .goreleaser.yaml | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 593003192..82a5a208a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -101,7 +101,6 @@ jobs: - name: Build Helm Binaries run: | - make build-cross make dist checksum VERSION="canary" - name: Upload Binaries diff --git a/.goreleaser.yaml b/.goreleaser.yaml index e7d93d01c..aedd77a1a 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -66,9 +66,7 @@ archives: {{- .ProjectName }}- {{- .Version }}- {{- .Os }}- - {{- if eq .Arch "amd64" }}x86_64 - {{- else if eq .Arch "386" }}i386 - {{- else }}{{ .Arch }}{{ end }} + {{- .Arch }} formats: - tar.gz - zip From eaa09100b9b18175d878b1e114cbe9df2a3f70c2 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Mon, 24 Nov 2025 05:20:57 -0700 Subject: [PATCH 302/571] fix: canary build file names Signed-off-by: Terry Howe --- .goreleaser.yaml | 2 +- Makefile | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index aedd77a1a..22bb4e379 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -78,7 +78,7 @@ checksum: name_template: 'checksums.txt' snapshot: - version_template: "{{ incpatch .Version }}-next" + version_template: "{{ if .Env.GORELEASER_CURRENT_TAG }}{{ .Env.GORELEASER_CURRENT_TAG }}{{ else }}{{ incpatch .Version }}-next{{ end }}" changelog: sort: asc diff --git a/Makefile b/Makefile index bca7b28d8..1e339fec4 100644 --- a/Makefile +++ b/Makefile @@ -179,7 +179,11 @@ build-cross: $(GORELEASER) .PHONY: dist dist: +ifeq ($(VERSION),canary) + GORELEASER_CURRENT_TAG=canary LDFLAGS='$(LDFLAGS)' $(GORELEASER) release --snapshot --clean +else LDFLAGS='$(LDFLAGS)' $(GORELEASER) release --snapshot --clean +endif .PHONY: fetch-dist fetch-dist: From e49a1dc16eee526928d8928b8d96c01ee513ebd9 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sat, 2 May 2026 10:34:04 -0600 Subject: [PATCH 303/571] fix: use index for optional env var in version_template Signed-off-by: Terry Howe --- .goreleaser.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 22bb4e379..c105e1813 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -78,7 +78,7 @@ checksum: name_template: 'checksums.txt' snapshot: - version_template: "{{ if .Env.GORELEASER_CURRENT_TAG }}{{ .Env.GORELEASER_CURRENT_TAG }}{{ else }}{{ incpatch .Version }}-next{{ end }}" + version_template: "{{ if index .Env \"GORELEASER_CURRENT_TAG\" }}{{ .Env.GORELEASER_CURRENT_TAG }}{{ else }}{{ incpatch .Version }}-next{{ end }}" changelog: sort: asc From 93103ce66cb6374d9d7b552802f53b21ea2c2dd1 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sat, 2 May 2026 11:25:43 -0600 Subject: [PATCH 304/571] fix: disable goreleaser checksums.txt and restrict zip to windows only Signed-off-by: Terry Howe --- .goreleaser.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index c105e1813..f99faced0 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -69,13 +69,17 @@ archives: {{- .Arch }} formats: - tar.gz - - zip + format_overrides: + - goos: windows + formats: + - tar.gz + - zip files: - LICENSE - README.md checksum: - name_template: 'checksums.txt' + disable: true snapshot: version_template: "{{ if index .Env \"GORELEASER_CURRENT_TAG\" }}{{ .Env.GORELEASER_CURRENT_TAG }}{{ else }}{{ incpatch .Version }}-next{{ end }}" From 04885dd905b6f8a823733dbc9b9f5cb2843a975f Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sat, 2 May 2026 11:35:35 -0600 Subject: [PATCH 305/571] fix: pass VERSION as GORELEASER_CURRENT_TAG to preserve v-prefix in archive names Signed-off-by: Terry Howe --- Makefile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 1e339fec4..250deac75 100644 --- a/Makefile +++ b/Makefile @@ -179,11 +179,7 @@ build-cross: $(GORELEASER) .PHONY: dist dist: -ifeq ($(VERSION),canary) - GORELEASER_CURRENT_TAG=canary LDFLAGS='$(LDFLAGS)' $(GORELEASER) release --snapshot --clean -else - LDFLAGS='$(LDFLAGS)' $(GORELEASER) release --snapshot --clean -endif + GORELEASER_CURRENT_TAG='$(VERSION)' LDFLAGS='$(LDFLAGS)' $(GORELEASER) release --snapshot --clean .PHONY: fetch-dist fetch-dist: From c075022ce16489f5f7afd45a37b679cf58fa36ea Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sat, 2 May 2026 11:50:15 -0600 Subject: [PATCH 306/571] fix: address goreleaser build issues flagged in review Signed-off-by: Terry Howe --- .github/workflows/release.yml | 1 - .goreleaser.yaml | 4 ---- Makefile | 3 +-- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 82a5a208a..01df0bda5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,6 @@ jobs: run: | set -eu -o pipefail - make build-cross VERSION="${{ github.ref_name }}" make dist checksum VERSION="${{ github.ref_name }}" - name: Set latest version diff --git a/.goreleaser.yaml b/.goreleaser.yaml index f99faced0..4046ee796 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -2,10 +2,6 @@ version: 2 project_name: helm -before: - hooks: - - go mod tidy - dist: _dist builds: - env: diff --git a/Makefile b/Makefile index 250deac75..c7fc2f1f0 100644 --- a/Makefile +++ b/Makefile @@ -163,7 +163,6 @@ gen-test-golden: test-unit # without a go.mod file when downloading the following dependencies $(GORELEASER): - echo go install github.com/goreleaser/goreleaser/v2@latest (cd /; go install github.com/goreleaser/goreleaser/v2@latest) $(GOIMPORTS): @@ -178,7 +177,7 @@ build-cross: $(GORELEASER) LDFLAGS='$(LDFLAGS)' $(GORELEASER) build --snapshot --clean .PHONY: dist -dist: +dist: $(GORELEASER) GORELEASER_CURRENT_TAG='$(VERSION)' LDFLAGS='$(LDFLAGS)' $(GORELEASER) release --snapshot --clean .PHONY: fetch-dist From 82899404a68f3826389bb38cf67bf75085db6b2c Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sat, 2 May 2026 12:12:38 -0600 Subject: [PATCH 307/571] ci: add fetch-depth 0 to canary checkout for goreleaser Signed-off-by: Terry Howe --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 01df0bda5..e50da2387 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,6 +85,8 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 + with: + fetch-depth: 0 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" From d199a1a42c04bccb287f2c7d9c3f73b669412e5a Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sat, 2 May 2026 12:25:35 -0600 Subject: [PATCH 308/571] chore: remove build-cross dependency from test-acceptance Signed-off-by: Terry Howe --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c7fc2f1f0..57365a025 100644 --- a/Makefile +++ b/Makefile @@ -130,8 +130,7 @@ test-source-headers: @scripts/validate-license.sh .PHONY: test-acceptance -test-acceptance: TARGETS = linux/amd64 -test-acceptance: build build-cross +test-acceptance: build @if [ -d "${ACCEPTANCE_DIR}" ]; then \ cd ${ACCEPTANCE_DIR} && \ ROBOT_RUN_TESTS=$(ACCEPTANCE_RUN_TESTS) ROBOT_HELM_PATH='$(BINDIR)' make acceptance; \ From 64aa46f2f1cf239cf6535c5e847e14dcb933a847 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sat, 2 May 2026 19:19:20 -0600 Subject: [PATCH 309/571] build: use goreleaser build with manual archive creation goreleaser v2 has a bug with no_unique_dist_dir where it registers archive tasks for all sub-arch variants even when constraints limit builds to one per arch, causing archive collision errors. Switch dist target to use goreleaser build (binaries only) and create tar.gz/zip archives manually, copying LICENSE and README.md into each platform directory to match the existing archive structure. Add sub-arch constraints (goamd64, goarm64, go386, goriscv64) to ensure only one variant is built per architecture. Signed-off-by: Terry Howe --- .goreleaser.yaml | 32 +++++++++----------------------- Makefile | 13 ++++++++++++- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 4046ee796..ab2089882 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -19,8 +19,16 @@ builds: - s390x - riscv64 - loong64 + goamd64: + - v1 goarm: - "7" + goarm64: + - v8.0 + go386: + - sse2 + goriscv64: + - rva20u64 ignore: - goos: darwin goarch: "386" @@ -55,28 +63,6 @@ builds: - -trimpath dir: . -archives: - - id: default - # this name template makes the OS and Arch compatible with the results of uname. - name_template: >- - {{- .ProjectName }}- - {{- .Version }}- - {{- .Os }}- - {{- .Arch }} - formats: - - tar.gz - format_overrides: - - goos: windows - formats: - - tar.gz - - zip - files: - - LICENSE - - README.md - -checksum: - disable: true - snapshot: version_template: "{{ if index .Env \"GORELEASER_CURRENT_TAG\" }}{{ .Env.GORELEASER_CURRENT_TAG }}{{ else }}{{ incpatch .Version }}-next{{ end }}" @@ -85,4 +71,4 @@ changelog: filters: exclude: - '^docs:' - - '^test:' \ No newline at end of file + - '^test:' diff --git a/Makefile b/Makefile index 57365a025..966380805 100644 --- a/Makefile +++ b/Makefile @@ -177,7 +177,18 @@ build-cross: $(GORELEASER) .PHONY: dist dist: $(GORELEASER) - GORELEASER_CURRENT_TAG='$(VERSION)' LDFLAGS='$(LDFLAGS)' $(GORELEASER) release --snapshot --clean + GORELEASER_CURRENT_TAG='$(VERSION)' LDFLAGS='$(LDFLAGS)' $(GORELEASER) build --snapshot --clean + @for platform_dir in _dist/*/; do \ + platform=$$(basename "$$platform_dir"); \ + { [ -f "_dist/$$platform/helm" ] || [ -f "_dist/$$platform/helm.exe" ]; } || continue; \ + cp LICENSE README.md "_dist/$$platform/"; \ + tar czf "_dist/helm-$(VERSION)-$$platform.tar.gz" -C _dist "$$platform/"; \ + done + @for platform_dir in _dist/windows-*/; do \ + [ -d "$$platform_dir" ] || continue; \ + platform=$$(basename "$$platform_dir"); \ + (cd _dist && zip -r "helm-$(VERSION)-$$platform.zip" "$$platform/"); \ + done .PHONY: fetch-dist fetch-dist: From f60ab7c31c81a73b8e0aade5aff41bfc01c08820 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sat, 2 May 2026 19:40:24 -0600 Subject: [PATCH 310/571] fix: add -extldflags -static to dist target to match build-cross Signed-off-by: Terry Howe --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 966380805..f68df780a 100644 --- a/Makefile +++ b/Makefile @@ -176,6 +176,7 @@ build-cross: $(GORELEASER) LDFLAGS='$(LDFLAGS)' $(GORELEASER) build --snapshot --clean .PHONY: dist +dist: LDFLAGS += -extldflags "-static" dist: $(GORELEASER) GORELEASER_CURRENT_TAG='$(VERSION)' LDFLAGS='$(LDFLAGS)' $(GORELEASER) build --snapshot --clean @for platform_dir in _dist/*/; do \ From 854f7f6b7217dcfe135df9e4652517d3ec9c3913 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Mon, 4 May 2026 13:57:52 -0700 Subject: [PATCH 311/571] fix: fetch logs from all containers in test pods When a test pod contains multiple containers (e.g. Istio/Consul/Vault sidecars), 'helm test --logs' failed with 'a container name must be specified'. This happened because GetPodLogs called the Kubernetes log API without specifying a container name. The fix fetches the pod spec first, then iterates over all containers (init containers + regular containers) and requests logs for each one explicitly. Errors from individual containers are collected and returned together via errors.Join rather than aborting on the first failure. Also fixes a typo: hooksByWight -> hooksByWeight. Closes #6902 Signed-off-by: Sebastien Tardif --- pkg/action/release_testing.go | 53 +++++++++++---- pkg/action/release_testing_test.go | 103 ++++++++++++++++++++++++++++- 2 files changed, 141 insertions(+), 15 deletions(-) diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index 043a41236..9de4e58f2 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -18,6 +18,7 @@ package action import ( "context" + "errors" "fmt" "io" "slices" @@ -25,6 +26,8 @@ import ( "time" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" chartutil "helm.sh/helm/v4/pkg/chart/v2/util" "helm.sh/helm/v4/pkg/kube" @@ -124,9 +127,9 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error { return fmt.Errorf("unable to get kubernetes client to fetch pod logs: %w", err) } - hooksByWight := append([]*release.Hook{}, rel.Hooks...) - sort.Stable(hookByWeight(hooksByWight)) - for _, h := range hooksByWight { + hooksByWeight := append([]*release.Hook{}, rel.Hooks...) + sort.Stable(hookByWeight(hooksByWeight)) + for _, h := range hooksByWeight { for _, e := range h.Events { if e == release.HookTest { if slices.Contains(r.Filters[ExcludeNameFilter], h.Name) { @@ -135,20 +138,42 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error { if len(r.Filters[IncludeNameFilter]) > 0 && !slices.Contains(r.Filters[IncludeNameFilter], h.Name) { continue } - req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{}) - logReader, err := req.Stream(context.Background()) - if err != nil { - return fmt.Errorf("unable to get pod logs for %s: %w", h.Name, err) - } - - fmt.Fprintf(out, "POD LOGS: %s\n", h.Name) - _, err = io.Copy(out, logReader) - fmt.Fprintln(out) - if err != nil { - return fmt.Errorf("unable to write pod logs for %s: %w", h.Name, err) + if err := r.getContainerLogs(out, client, h.Name); err != nil { + return err } } } } return nil } + +// getContainerLogs fetches logs from all containers (init and regular) in the +// named pod and writes them to out. It continues on per-container errors and +// returns all of them joined at the end. +func (r *ReleaseTesting) getContainerLogs(out io.Writer, client kubernetes.Interface, podName string) error { + pod, err := client.CoreV1().Pods(r.Namespace).Get(context.Background(), podName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("unable to get pod %s: %w", podName, err) + } + + allContainers := append(pod.Spec.InitContainers, pod.Spec.Containers...) + + var errs []error + for _, c := range allContainers { + opts := &v1.PodLogOptions{Container: c.Name} + req := client.CoreV1().Pods(r.Namespace).GetLogs(podName, opts) + logReader, err := req.Stream(context.Background()) + if err != nil { + errs = append(errs, fmt.Errorf("unable to get logs for pod %s, container %s: %w", podName, c.Name, err)) + continue + } + + fmt.Fprintf(out, "POD LOGS: %s (%s)\n", podName, c.Name) + _, err = io.Copy(out, logReader) + fmt.Fprintln(out) + if err != nil { + errs = append(errs, fmt.Errorf("unable to write logs for pod %s, container %s: %w", podName, c.Name, err)) + } + } + return errors.Join(errs...) +} diff --git a/pkg/action/release_testing_test.go b/pkg/action/release_testing_test.go index ab35e104a..ea647af80 100644 --- a/pkg/action/release_testing_test.go +++ b/pkg/action/release_testing_test.go @@ -22,10 +22,14 @@ import ( "errors" "io" "os" + "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + fakeclientset "k8s.io/client-go/kubernetes/fake" "helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/kube" @@ -89,7 +93,7 @@ func TestReleaseTestingGetPodLogs_PodRetrievalError(t *testing.T) { }, } - require.ErrorContains(t, client.GetPodLogs(&bytes.Buffer{}, &release.Release{Hooks: hooks}), "unable to get pod logs") + require.ErrorContains(t, client.GetPodLogs(&bytes.Buffer{}, &release.Release{Hooks: hooks}), "unable to get pod") } func TestReleaseTesting_WaitOptionsPassedDownstream(t *testing.T) { @@ -117,3 +121,100 @@ func TestReleaseTesting_WaitOptionsPassedDownstream(t *testing.T) { // Verify that WaitOptions were passed to GetWaiter is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") } + +func TestGetContainerLogs_MultipleContainers(t *testing.T) { + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + Namespace: "default", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + {Name: "main"}, + {Name: "sidecar"}, + }, + }, + } + + client := fakeclientset.NewClientset(pod) + rt := &ReleaseTesting{Namespace: "default"} + + var buf bytes.Buffer + err := rt.getContainerLogs(&buf, client, "test-pod") + // The fake client doesn't serve real log streams, so we expect + // per-container errors rather than success, but critically it should + // NOT fail with "a container name must be specified". + if err != nil { + assert.NotContains(t, err.Error(), "a container name must be specified") + assert.Contains(t, err.Error(), "container main") + assert.Contains(t, err.Error(), "container sidecar") + } +} + +func TestGetContainerLogs_WithInitContainers(t *testing.T) { + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + Namespace: "default", + }, + Spec: v1.PodSpec{ + InitContainers: []v1.Container{ + {Name: "init-setup"}, + }, + Containers: []v1.Container{ + {Name: "main"}, + }, + }, + } + + client := fakeclientset.NewClientset(pod) + rt := &ReleaseTesting{Namespace: "default"} + + var buf bytes.Buffer + err := rt.getContainerLogs(&buf, client, "test-pod") + if err != nil { + // Both init and regular containers should be attempted + assert.Contains(t, err.Error(), "container init-setup") + assert.Contains(t, err.Error(), "container main") + } +} + +func TestGetContainerLogs_PodNotFound(t *testing.T) { + client := fakeclientset.NewClientset() + rt := &ReleaseTesting{Namespace: "default"} + + var buf bytes.Buffer + err := rt.getContainerLogs(&buf, client, "nonexistent-pod") + require.Error(t, err) + assert.Contains(t, err.Error(), "unable to get pod nonexistent-pod") +} + +func TestGetPodLogs_MultiContainerOutput(t *testing.T) { + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "multi-test", + Namespace: "default", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + {Name: "container-a"}, + {Name: "container-b"}, + }, + }, + } + + client := fakeclientset.NewClientset(pod) + rt := &ReleaseTesting{ + Namespace: "default", + Filters: map[string][]string{}, + } + + // Call getContainerLogs directly to test output formatting + var buf bytes.Buffer + _ = rt.getContainerLogs(&buf, client, "multi-test") + output := buf.String() + // Even if logs fail, check that header formatting uses container names + if len(output) > 0 { + assert.True(t, strings.Contains(output, "(container-a)") || strings.Contains(output, "(container-b)")) + } +} From 922558fc1a3b3f2c2b7e8ad0f32bd5bcd46ca70e Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Mon, 4 May 2026 14:04:25 -0700 Subject: [PATCH 312/571] fix: address review feedback - Close log stream after reading (prevents connection/fd leak) - Strengthen tests to assert on output headers rather than error paths - Remove unused import Signed-off-by: Sebastien Tardif --- pkg/action/release_testing.go | 1 + pkg/action/release_testing_test.go | 40 +++++++++++------------------- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index 9de4e58f2..8cb6ce664 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -170,6 +170,7 @@ func (r *ReleaseTesting) getContainerLogs(out io.Writer, client kubernetes.Inter fmt.Fprintf(out, "POD LOGS: %s (%s)\n", podName, c.Name) _, err = io.Copy(out, logReader) + logReader.Close() fmt.Fprintln(out) if err != nil { errs = append(errs, fmt.Errorf("unable to write logs for pod %s, container %s: %w", podName, c.Name, err)) diff --git a/pkg/action/release_testing_test.go b/pkg/action/release_testing_test.go index ea647af80..dcc708548 100644 --- a/pkg/action/release_testing_test.go +++ b/pkg/action/release_testing_test.go @@ -22,7 +22,6 @@ import ( "errors" "io" "os" - "strings" "testing" "github.com/stretchr/testify/assert" @@ -141,14 +140,10 @@ func TestGetContainerLogs_MultipleContainers(t *testing.T) { var buf bytes.Buffer err := rt.getContainerLogs(&buf, client, "test-pod") - // The fake client doesn't serve real log streams, so we expect - // per-container errors rather than success, but critically it should - // NOT fail with "a container name must be specified". - if err != nil { - assert.NotContains(t, err.Error(), "a container name must be specified") - assert.Contains(t, err.Error(), "container main") - assert.Contains(t, err.Error(), "container sidecar") - } + require.NoError(t, err) + output := buf.String() + assert.Contains(t, output, "POD LOGS: test-pod (main)") + assert.Contains(t, output, "POD LOGS: test-pod (sidecar)") } func TestGetContainerLogs_WithInitContainers(t *testing.T) { @@ -172,11 +167,11 @@ func TestGetContainerLogs_WithInitContainers(t *testing.T) { var buf bytes.Buffer err := rt.getContainerLogs(&buf, client, "test-pod") - if err != nil { - // Both init and regular containers should be attempted - assert.Contains(t, err.Error(), "container init-setup") - assert.Contains(t, err.Error(), "container main") - } + require.NoError(t, err) + output := buf.String() + // Init containers should appear before regular containers + assert.Contains(t, output, "POD LOGS: test-pod (init-setup)") + assert.Contains(t, output, "POD LOGS: test-pod (main)") } func TestGetContainerLogs_PodNotFound(t *testing.T) { @@ -189,7 +184,7 @@ func TestGetContainerLogs_PodNotFound(t *testing.T) { assert.Contains(t, err.Error(), "unable to get pod nonexistent-pod") } -func TestGetPodLogs_MultiContainerOutput(t *testing.T) { +func TestGetContainerLogs_OutputHeaderFormat(t *testing.T) { pod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "multi-test", @@ -204,17 +199,12 @@ func TestGetPodLogs_MultiContainerOutput(t *testing.T) { } client := fakeclientset.NewClientset(pod) - rt := &ReleaseTesting{ - Namespace: "default", - Filters: map[string][]string{}, - } + rt := &ReleaseTesting{Namespace: "default"} - // Call getContainerLogs directly to test output formatting var buf bytes.Buffer - _ = rt.getContainerLogs(&buf, client, "multi-test") + err := rt.getContainerLogs(&buf, client, "multi-test") + require.NoError(t, err) output := buf.String() - // Even if logs fail, check that header formatting uses container names - if len(output) > 0 { - assert.True(t, strings.Contains(output, "(container-a)") || strings.Contains(output, "(container-b)")) - } + assert.Contains(t, output, "POD LOGS: multi-test (container-a)") + assert.Contains(t, output, "POD LOGS: multi-test (container-b)") } From e23bf3af53c52185123278e83b7023c102707778 Mon Sep 17 00:00:00 2001 From: Scott Rigby Date: Mon, 4 May 2026 19:46:58 -0400 Subject: [PATCH 313/571] build: Clean up Goreleaser change (#32098) Makefile: - restore dist target - remove unused LDFLAGS added to dist target - remove unused TARGETS var gh release action: - restore build-cross to release and canary-release jobs Signed-off-by: Scott Rigby Co-authored-by: Terry Howe --- .github/workflows/release.yml | 2 ++ Makefile | 23 ++++++++--------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e50da2387..ab8a4a509 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,6 +38,7 @@ jobs: run: | set -eu -o pipefail + make build-cross VERSION="${{ github.ref_name }}" make dist checksum VERSION="${{ github.ref_name }}" - name: Set latest version @@ -102,6 +103,7 @@ jobs: - name: Build Helm Binaries run: | + make build-cross make dist checksum VERSION="canary" - name: Upload Binaries diff --git a/Makefile b/Makefile index f68df780a..81b149a68 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ BINDIR := $(CURDIR)/bin INSTALL_PATH ?= /usr/local/bin DIST_DIRS := find * -type d -exec -TARGETS := darwin/amd64 darwin/arm64 linux/amd64 linux/386 linux/arm linux/arm64 linux/loong64 linux/ppc64le linux/s390x linux/riscv64 windows/amd64 windows/arm64 TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum darwin-arm64.tar.gz darwin-arm64.tar.gz.sha256 darwin-arm64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-loong64.tar.gz linux-loong64.tar.gz.sha256 linux-loong64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum linux-riscv64.tar.gz linux-riscv64.tar.gz.sha256 linux-riscv64.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum windows-arm64.zip windows-arm64.zip.sha256 windows-arm64.zip.sha256sum BINNAME ?= helm @@ -176,20 +175,14 @@ build-cross: $(GORELEASER) LDFLAGS='$(LDFLAGS)' $(GORELEASER) build --snapshot --clean .PHONY: dist -dist: LDFLAGS += -extldflags "-static" -dist: $(GORELEASER) - GORELEASER_CURRENT_TAG='$(VERSION)' LDFLAGS='$(LDFLAGS)' $(GORELEASER) build --snapshot --clean - @for platform_dir in _dist/*/; do \ - platform=$$(basename "$$platform_dir"); \ - { [ -f "_dist/$$platform/helm" ] || [ -f "_dist/$$platform/helm.exe" ]; } || continue; \ - cp LICENSE README.md "_dist/$$platform/"; \ - tar czf "_dist/helm-$(VERSION)-$$platform.tar.gz" -C _dist "$$platform/"; \ - done - @for platform_dir in _dist/windows-*/; do \ - [ -d "$$platform_dir" ] || continue; \ - platform=$$(basename "$$platform_dir"); \ - (cd _dist && zip -r "helm-$(VERSION)-$$platform.zip" "$$platform/"); \ - done +dist: + ( \ + cd _dist && \ + $(DIST_DIRS) cp ../LICENSE {} \; && \ + $(DIST_DIRS) cp ../README.md {} \; && \ + $(DIST_DIRS) tar -zcf helm-${VERSION}-{}.tar.gz {} \; && \ + $(DIST_DIRS) zip -r helm-${VERSION}-{}.zip {} \; \ + ) .PHONY: fetch-dist fetch-dist: From 9728c5af31c7039e9806037c4e59e46eeb43d79c Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Tue, 5 May 2026 18:09:23 +0100 Subject: [PATCH 314/571] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Evans Mungai --- pkg/action/validate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/action/validate.go b/pkg/action/validate.go index 102259bf1..88771053b 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -95,7 +95,7 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN if err := checkOwnership(existing, releaseName, releaseNamespace); err != nil { return fmt.Errorf("%s exists and cannot be imported into the current release: %s", resourceString(info), err) } - +// Resources that are not found are skipped because they are already deleted and do not need deletion. infoCopy := *info requireUpdate.Append(&infoCopy) return nil From 007713087bd4f381bb8b74f66f35af9e3dafa376 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Tue, 5 May 2026 19:02:23 +0100 Subject: [PATCH 315/571] fix: address Copilot review on ownership-verified uninstall - Distinguish unowned resources from those whose ownership could not be verified (e.g. RBAC/network errors). Unverifiable resources are now returned in a separate list with the underlying error so log output and the "kept" summary describe what actually happened. - Only prefix the kept summary with the resource-policy header when there are policy-kept resources, so users with only ownership-skipped resources don't see a misleading header. - Update verifyOwnershipBeforeDelete doc comment to reflect that not-found resources are excluded from all returned lists. - Assert success of Releases.Create in uninstall tests so fixture setup failures are surfaced immediately. Signed-off-by: Evans Mungai --- pkg/action/uninstall.go | 52 ++++++++++++++++++++++++++++++------ pkg/action/uninstall_test.go | 8 +++--- pkg/action/validate.go | 28 ++++++++++++++----- pkg/action/validate_test.go | 12 ++++----- 4 files changed, 75 insertions(+), 25 deletions(-) diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index 73927ed9d..b14305112 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -102,7 +102,7 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error) resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false) if err == nil && len(resources) > 0 { - ownedResources, unownedResources, err := verifyOwnershipBeforeDelete(resources, r.Name, r.Namespace) + ownedResources, unownedResources, unverifiableResources, err := verifyOwnershipBeforeDelete(resources, r.Name, r.Namespace) if err == nil { if len(unownedResources) > 0 { u.cfg.Logger().Warn("dry-run: resources would be skipped because they are not owned by this release", @@ -116,6 +116,19 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error) } } + if len(unverifiableResources) > 0 { + u.cfg.Logger().Warn("dry-run: resources would be skipped because their ownership could not be verified", + "release", r.Name, + "count", len(unverifiableResources)) + for _, ur := range unverifiableResources { + u.cfg.Logger().Warn("dry-run: would skip resource (ownership could not be verified)", + "kind", ur.Info.Mapping.GroupVersionKind.Kind, + "name", ur.Info.Name, + "namespace", ur.Info.Namespace, + "error", ur.Err) + } + } + if len(ownedResources) > 0 { u.cfg.Logger().Debug("dry-run: resources would be deleted", "release", r.Name, @@ -207,9 +220,6 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error) return nil, fmt.Errorf("failed to delete release: %s", name) } - if kept != "" { - kept = "These resources were kept due to the resource policy:\n" + kept - } res.Info = kept if err := waiter.WaitForDelete(deletedResources, u.Timeout); err != nil { @@ -323,8 +333,11 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri filesToKeep, filesToDelete := filterManifestsToKeep(files) var kept strings.Builder - for _, f := range filesToKeep { - fmt.Fprintf(&kept, "[%s] %s\n", f.Head.Kind, f.Head.Metadata.Name) + if len(filesToKeep) > 0 { + kept.WriteString("These resources were kept due to the resource policy:\n") + for _, f := range filesToKeep { + fmt.Fprintf(&kept, "[%s] %s\n", f.Head.Kind, f.Head.Metadata.Name) + } } var builder strings.Builder @@ -339,8 +352,9 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri // Verify ownership before deleting resources var ownedResources, unownedResources kube.ResourceList + var unverifiableResources []unverifiableResource if len(resources) > 0 { - ownedResources, unownedResources, err = verifyOwnershipBeforeDelete(resources, rel.Name, rel.Namespace) + ownedResources, unownedResources, unverifiableResources, err = verifyOwnershipBeforeDelete(resources, rel.Name, rel.Namespace) if err != nil { return nil, "", []error{fmt.Errorf("unable to verify resource ownership: %w", err)} } @@ -354,12 +368,34 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri "namespace", info.Namespace, "release", rel.Name) } - kept.WriteString(fmt.Sprintf("\n%d resource(s) were not deleted because they are not owned by this release:\n", len(unownedResources))) + if kept.Len() > 0 { + kept.WriteString("\n") + } + fmt.Fprintf(&kept, "%d resource(s) were not deleted because they are not owned by this release:\n", len(unownedResources)) for _, info := range unownedResources { fmt.Fprintf(&kept, "[%s] %s\n", info.Mapping.GroupVersionKind.Kind, info.Name) } } + // Log warnings for resources whose ownership could not be verified + if len(unverifiableResources) > 0 { + for _, ur := range unverifiableResources { + u.cfg.Logger().Warn("skipping delete of resource because ownership could not be verified", + "kind", ur.Info.Mapping.GroupVersionKind.Kind, + "name", ur.Info.Name, + "namespace", ur.Info.Namespace, + "release", rel.Name, + "error", ur.Err) + } + if kept.Len() > 0 { + kept.WriteString("\n") + } + fmt.Fprintf(&kept, "%d resource(s) were not deleted because their ownership could not be verified:\n", len(unverifiableResources)) + for _, ur := range unverifiableResources { + fmt.Fprintf(&kept, "[%s] %s: %s\n", ur.Info.Mapping.GroupVersionKind.Kind, ur.Info.Name, ur.Err) + } + } + // Delete only owned resources if len(ownedResources) > 0 { for _, info := range ownedResources { diff --git a/pkg/action/uninstall_test.go b/pkg/action/uninstall_test.go index 2bf3d3878..3b6d09c33 100644 --- a/pkg/action/uninstall_test.go +++ b/pkg/action/uninstall_test.go @@ -148,7 +148,7 @@ func TestUninstallRelease_Cascade(t *testing.T) { "password": "password" } }` - unAction.cfg.Releases.Create(rel) + require.NoError(t, unAction.cfg.Releases.Create(rel)) // Create dummy resources with Mapping but no Client - this skips ownership verification // (nil Client is treated as owned) and goes directly to delete @@ -208,7 +208,7 @@ metadata: meta.helm.sh/release-namespace: default data: key: value` - config.Releases.Create(rel) + require.NoError(t, config.Releases.Create(rel)) // Create dummy resources with proper ownership metadata labels := map[string]string{ @@ -281,7 +281,7 @@ metadata: meta.helm.sh/release-namespace: default data: key: value` - config.Releases.Create(rel) + require.NoError(t, config.Releases.Create(rel)) // Create dummy resources - one unowned to test logging dummyResources := kube.ResourceList{ @@ -330,7 +330,7 @@ metadata: meta.helm.sh/release-namespace: default data: key: value` - config.Releases.Create(rel) + require.NoError(t, config.Releases.Create(rel)) // Create dummy resources - one unowned to test dry-run logging dummyResources := kube.ResourceList{ diff --git a/pkg/action/validate.go b/pkg/action/validate.go index 88771053b..bc6cb4e9c 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -104,12 +104,26 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN return requireUpdate, err } +// unverifiableResource pairs a resource with the error encountered while attempting +// to verify its ownership (for example, RBAC or network failures). +type unverifiableResource struct { + Info *resource.Info + Err error +} + // verifyOwnershipBeforeDelete checks that resources in the list are owned by the specified release. -// It returns two lists: owned resources (safe to delete) and unowned resources (should skip). -// Resources that are not found are considered owned (already deleted, safe to attempt delete). -func verifyOwnershipBeforeDelete(resources kube.ResourceList, releaseName, releaseNamespace string) (kube.ResourceList, kube.ResourceList, error) { +// It returns three lists: +// - owned: resources confirmed to be owned by the release (safe to delete). +// - unowned: resources that exist but are not owned by the release (should be skipped). +// - unverifiable: resources whose ownership could not be determined due to a fetch +// error (e.g. RBAC or network issues), paired with the underlying error. +// +// Resources that are not found on the server are excluded from all returned lists, +// since they have already been deleted and require no further action. +func verifyOwnershipBeforeDelete(resources kube.ResourceList, releaseName, releaseNamespace string) (kube.ResourceList, kube.ResourceList, []unverifiableResource, error) { var owned kube.ResourceList var unowned kube.ResourceList + var unverifiable []unverifiableResource err := resources.Visit(func(info *resource.Info, err error) error { if err != nil { @@ -127,12 +141,12 @@ func verifyOwnershipBeforeDelete(resources kube.ResourceList, releaseName, relea existing, err := helper.Get(info.Namespace, info.Name) if err != nil { if apierrors.IsNotFound(err) { - // Resource already deleted, skip deletion + // Resource already deleted; nothing to do. return nil } - // Cannot fetch resource (network/permission issue), cannot verify ownership + // Cannot fetch resource (network/permission issue); ownership unverifiable. infoCopy := *info - unowned.Append(&infoCopy) + unverifiable = append(unverifiable, unverifiableResource{Info: &infoCopy, Err: err}) return nil } @@ -150,7 +164,7 @@ func verifyOwnershipBeforeDelete(resources kube.ResourceList, releaseName, relea return nil }) - return owned, unowned, err + return owned, unowned, unverifiable, err } func checkOwnership(obj runtime.Object, releaseName, releaseNamespace string) error { diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go index b425299f5..1cb1002d6 100644 --- a/pkg/action/validate_test.go +++ b/pkg/action/validate_test.go @@ -234,7 +234,7 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { owned2 := newDeploymentWithOwner("owned2", "ns-a", labels, annotations) resources := kube.ResourceList{owned1, owned2} - ownedList, unownedList, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) + ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) assert.NoError(t, err) assert.Len(t, ownedList, 2) assert.Len(t, unownedList, 0) @@ -246,7 +246,7 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { unowned := newDeploymentWithOwner("unowned", "ns-a", labels, wrongAnnotations) resources := kube.ResourceList{owned, unowned} - ownedList, unownedList, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) + ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) assert.NoError(t, err) assert.Len(t, ownedList, 1) assert.Len(t, unownedList, 1) @@ -259,7 +259,7 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { missing := newMissingDeployment("missing", "ns-a") resources := kube.ResourceList{missing} - ownedList, unownedList, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) + ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) assert.NoError(t, err) assert.Len(t, ownedList, 0) assert.Len(t, unownedList, 0) @@ -270,7 +270,7 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { noMeta := newDeploymentWithOwner("no-meta", "ns-a", nil, nil) resources := kube.ResourceList{noMeta} - ownedList, unownedList, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) + ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) assert.NoError(t, err) assert.Len(t, ownedList, 0) assert.Len(t, unownedList, 1) @@ -281,7 +281,7 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { otherRelease := newDeploymentWithOwner("other", "ns-a", labels, wrongAnnotations) resources := kube.ResourceList{otherRelease} - ownedList, unownedList, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) + ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) assert.NoError(t, err) assert.Len(t, ownedList, 0) assert.Len(t, unownedList, 1) @@ -294,7 +294,7 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { missing := newMissingDeployment("missing", "ns-a") resources := kube.ResourceList{owned, unowned, missing} - ownedList, unownedList, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) + ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) assert.NoError(t, err) assert.Len(t, ownedList, 1) assert.Len(t, unownedList, 1) From 03a65a71f7397620315747ecac4ba60b73f63623 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Tue, 5 May 2026 23:13:14 +0100 Subject: [PATCH 316/571] Fix lint errors Signed-off-by: Evans Mungai --- internal/chart/v3/util/dependencies.go | 5 +++-- pkg/action/hooks.go | 4 ++-- pkg/action/uninstall_test.go | 3 +-- pkg/action/validate.go | 2 +- pkg/chart/v2/util/dependencies.go | 5 +++-- pkg/cmd/history.go | 5 +++-- pkg/repo/v1/index.go | 17 +++++++++-------- 7 files changed, 22 insertions(+), 19 deletions(-) diff --git a/internal/chart/v3/util/dependencies.go b/internal/chart/v3/util/dependencies.go index 9c4d8e80f..b31f7eb96 100644 --- a/internal/chart/v3/util/dependencies.go +++ b/internal/chart/v3/util/dependencies.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "log/slog" + "slices" "strings" chart "helm.sh/helm/v4/internal/chart/v3" @@ -242,8 +243,8 @@ func set(path []string, data map[string]any) map[string]any { return nil } cur := data - for i := len(path) - 1; i >= 0; i-- { - cur = map[string]any{path[i]: cur} + for _, v := range slices.Backward(path) { + cur = map[string]any{v: cur} } return cur } diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index a4a8da7a6..e7be37bd8 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -150,8 +150,8 @@ func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook return func() error { // If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted // or output should be logged under succeeded condition. If so, then clear the corresponding resource object in each hook - for i := len(executingHooks) - 1; i >= 0; i-- { - h := executingHooks[i] + for _, v := range slices.Backward(executingHooks) { + h := v if err := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnSucceeded); err != nil { // We log here as we still want to attempt hook resource deletion even if output logging fails. log.Printf("error outputting logs for hook failure: %v", err) diff --git a/pkg/action/uninstall_test.go b/pkg/action/uninstall_test.go index 3b6d09c33..90f69d639 100644 --- a/pkg/action/uninstall_test.go +++ b/pkg/action/uninstall_test.go @@ -19,7 +19,6 @@ package action import ( "bytes" "errors" - "fmt" "io" "log/slog" "testing" @@ -157,7 +156,7 @@ func TestUninstallRelease_Cascade(t *testing.T) { } failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.DeleteError = fmt.Errorf("Uninstall with cascade failed") + failer.DeleteError = errors.New("Uninstall with cascade failed") failer.DummyResources = dummyResources unAction.cfg.KubeClient = failer _, err := unAction.Run(rel.Name) diff --git a/pkg/action/validate.go b/pkg/action/validate.go index bc6cb4e9c..cce8e32ee 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -95,7 +95,7 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN if err := checkOwnership(existing, releaseName, releaseNamespace); err != nil { return fmt.Errorf("%s exists and cannot be imported into the current release: %s", resourceString(info), err) } -// Resources that are not found are skipped because they are already deleted and do not need deletion. + // Resources that are not found are skipped because they are already deleted and do not need deletion. infoCopy := *info requireUpdate.Append(&infoCopy) return nil diff --git a/pkg/chart/v2/util/dependencies.go b/pkg/chart/v2/util/dependencies.go index abd673f9d..f28a4f4b1 100644 --- a/pkg/chart/v2/util/dependencies.go +++ b/pkg/chart/v2/util/dependencies.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "log/slog" + "slices" "strings" "helm.sh/helm/v4/internal/copystructure" @@ -242,8 +243,8 @@ func set(path []string, data map[string]any) map[string]any { return nil } cur := data - for i := len(path) - 1; i >= 0; i-- { - cur = map[string]any{path[i]: cur} + for _, v := range slices.Backward(path) { + cur = map[string]any{v: cur} } return cur } diff --git a/pkg/cmd/history.go b/pkg/cmd/history.go index 3349b7bc1..7c0f3ecde 100644 --- a/pkg/cmd/history.go +++ b/pkg/cmd/history.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "io" + "slices" "strconv" "time" @@ -207,8 +208,8 @@ func getHistory(client *action.History, name string) (releaseHistory, error) { } func getReleaseHistory(rls []*release.Release) (history releaseHistory) { - for i := len(rls) - 1; i >= 0; i-- { - r := rls[i] + for _, v := range slices.Backward(rls) { + r := v c := formatChartName(r.Chart) s := r.Info.Status.String() v := r.Version diff --git a/pkg/repo/v1/index.go b/pkg/repo/v1/index.go index 3dbdf7dfc..825dc2753 100644 --- a/pkg/repo/v1/index.go +++ b/pkg/repo/v1/index.go @@ -25,6 +25,7 @@ import ( "os" "path" "path/filepath" + "slices" "sort" "strings" "time" @@ -356,21 +357,21 @@ func loadIndex(data []byte, source string) (*IndexFile, error) { } for name, cvs := range i.Entries { - for idx := len(cvs) - 1; idx >= 0; idx-- { - if cvs[idx] == nil { + for idx, v := range slices.Backward(cvs) { + if v == nil { slog.Warn(fmt.Sprintf("skipping loading invalid entry for chart %q from %s: empty entry", name, source)) cvs = append(cvs[:idx], cvs[idx+1:]...) continue } // When metadata section missing, initialize with no data - if cvs[idx].Metadata == nil { - cvs[idx].Metadata = &chart.Metadata{} + if v.Metadata == nil { + v.Metadata = &chart.Metadata{} } - if cvs[idx].APIVersion == "" { - cvs[idx].APIVersion = chart.APIVersionV1 + if v.APIVersion == "" { + v.APIVersion = chart.APIVersionV1 } - if err := cvs[idx].Validate(); ignoreSkippableChartValidationError(err) != nil { - slog.Warn(fmt.Sprintf("skipping loading invalid entry for chart %q %q from %s: %s", name, cvs[idx].Version, source, err)) + if err := v.Validate(); ignoreSkippableChartValidationError(err) != nil { + slog.Warn(fmt.Sprintf("skipping loading invalid entry for chart %q %q from %s: %s", name, v.Version, source, err)) cvs = append(cvs[:idx], cvs[idx+1:]...) } } From 19b3656306829cae166f484feb4a1319f47844c0 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Wed, 6 May 2026 10:35:57 -0700 Subject: [PATCH 317/571] Bump to version v4.2 (#32102) Signed-off-by: George Jenkins --- internal/version/version.go | 2 +- pkg/chart/common/capabilities_test.go | 4 ++-- pkg/cmd/testdata/output/version-short.txt | 2 +- pkg/cmd/testdata/output/version-template.txt | 2 +- pkg/cmd/testdata/output/version.txt | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/version/version.go b/internal/version/version.go index 3daf80893..007f79f16 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -34,7 +34,7 @@ var ( // // Increment major number for new feature additions and behavioral changes. // Increment minor number for bug fixes and performance enhancements. - version = "v4.1" + version = "v4.2" // metadata is extra build time data metadata = "" diff --git a/pkg/chart/common/capabilities_test.go b/pkg/chart/common/capabilities_test.go index c9f026cbb..c8d47581c 100644 --- a/pkg/chart/common/capabilities_test.go +++ b/pkg/chart/common/capabilities_test.go @@ -60,8 +60,8 @@ func TestDefaultCapabilities(t *testing.T) { } hv := caps.HelmVersion - if hv.Version != "v4.1" { - t.Errorf("Expected default HelmVersion to be v4.1, got %q", hv.Version) + if hv.Version != "v4.2" { + t.Errorf("Expected default HelmVersion to be v4.2, got %q", hv.Version) } } diff --git a/pkg/cmd/testdata/output/version-short.txt b/pkg/cmd/testdata/output/version-short.txt index 8cf4318fb..2fa2c5705 100644 --- a/pkg/cmd/testdata/output/version-short.txt +++ b/pkg/cmd/testdata/output/version-short.txt @@ -1 +1 @@ -v4.1 +v4.2 diff --git a/pkg/cmd/testdata/output/version-template.txt b/pkg/cmd/testdata/output/version-template.txt index 8fd8b4962..8f2491e54 100644 --- a/pkg/cmd/testdata/output/version-template.txt +++ b/pkg/cmd/testdata/output/version-template.txt @@ -1 +1 @@ -Version: v4.1 \ No newline at end of file +Version: v4.2 \ No newline at end of file diff --git a/pkg/cmd/testdata/output/version.txt b/pkg/cmd/testdata/output/version.txt index 1f4cf4d4a..331c6d5e9 100644 --- a/pkg/cmd/testdata/output/version.txt +++ b/pkg/cmd/testdata/output/version.txt @@ -1 +1 @@ -version.BuildInfo{Version:"v4.1", GitCommit:"", GitTreeState:"", GoVersion:"", KubeClientVersion:"v1.20"} +version.BuildInfo{Version:"v4.2", GitCommit:"", GitTreeState:"", GoVersion:"", KubeClientVersion:"v1.20"} From d9b2716be5fbb3309c3176b0c887d91ffb16c048 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 21:33:30 +0000 Subject: [PATCH 318/571] chore(deps): bump github/codeql-action from 4.35.3 to 4.35.4 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.3 to 4.35.4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/e46ed2cbd01164d986452f91f178727624ae40d7...68bde559dea0fdcac2102bfdf6230c5f70eb485e) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.35.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 6714fc8de..256d26cc3 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # pinv4.35.3 + uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # pinv4.35.4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@e46ed2cbd01164d986452f91f178727624ae40d7 # pinv4.35.3 + uses: github/codeql-action/autobuild@68bde559dea0fdcac2102bfdf6230c5f70eb485e # pinv4.35.4 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # pinv4.35.3 + uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # pinv4.35.4 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 5d78ddd61..16a9a8c13 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3 + uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 with: sarif_file: results.sarif From 2cc69251d81ad94e9e0edbea6c31dedf4e2c25d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 21:34:37 +0000 Subject: [PATCH 319/571] chore(deps): bump golang.org/x/crypto from 0.50.0 to 0.51.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.50.0 to 0.51.0. - [Commits](https://github.com/golang/crypto/compare/v0.50.0...v0.51.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.51.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 40c02c7e1..4fd2ecde8 100644 --- a/go.mod +++ b/go.mod @@ -35,9 +35,9 @@ require ( github.com/stretchr/testify v1.11.1 github.com/tetratelabs/wazero v1.11.0 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.50.0 - golang.org/x/term v0.42.0 - golang.org/x/text v0.36.0 + golang.org/x/crypto v0.51.0 + golang.org/x/term v0.43.0 + golang.org/x/text v0.37.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.36.0 k8s.io/apiextensions-apiserver v0.36.0 @@ -157,13 +157,13 @@ require ( go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/mod v0.34.0 // indirect - golang.org/x/net v0.52.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.53.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.43.0 // indirect + golang.org/x/sys v0.44.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.43.0 // indirect + golang.org/x/tools v0.44.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/grpc v1.80.0 // indirect diff --git a/go.sum b/go.sum index f1a2ca9f8..50373d1f4 100644 --- a/go.sum +++ b/go.sum @@ -382,14 +382,14 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -400,8 +400,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -430,8 +430,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -439,8 +439,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -448,8 +448,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -458,8 +458,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= From 6620fec5d19f6c86425501ee64f8cb8a8fb3fa3e Mon Sep 17 00:00:00 2001 From: Sumit Solanki Date: Sat, 9 May 2026 12:17:40 +0530 Subject: [PATCH 320/571] fix(downloader): order DiskCache.Get checks for overlayfs empty dirs Signed-off-by: Sumit Solanki --- pkg/downloader/cache.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/downloader/cache.go b/pkg/downloader/cache.go index 1e23fbfcd..c628ba95d 100644 --- a/pkg/downloader/cache.go +++ b/pkg/downloader/cache.go @@ -59,15 +59,17 @@ func (c *DiskCache) Get(key [sha256.Size]byte, cacheType string) (string, error) if err != nil { return "", err } - // Empty files treated as not exist because there is no content. - if fi.Size() == 0 { - return p, os.ErrNotExist - } // directories should never happen unless something outside helm is operating // on this content. if fi.IsDir() { return p, errors.New("is a directory") } + // Empty files treated as not exist because there is no content. + // IsDir must be checked first: some filesystems (e.g. overlay) report + // directory size as 0. + if fi.Size() == 0 { + return p, os.ErrNotExist + } return p, nil } From 53d5f13f46f7dd555be8bd90616abdc77d470000 Mon Sep 17 00:00:00 2001 From: Sumit Solanki Date: Sat, 9 May 2026 12:28:41 +0530 Subject: [PATCH 321/571] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Sumit Solanki --- pkg/downloader/cache.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/downloader/cache.go b/pkg/downloader/cache.go index c628ba95d..92d477e49 100644 --- a/pkg/downloader/cache.go +++ b/pkg/downloader/cache.go @@ -64,8 +64,8 @@ func (c *DiskCache) Get(key [sha256.Size]byte, cacheType string) (string, error) if fi.IsDir() { return p, errors.New("is a directory") } - // Empty files treated as not exist because there is no content. - // IsDir must be checked first: some filesystems (e.g. overlay) report + // Empty files are treated as non-existent because there is no content. + // IsDir must be checked first: some filesystems (e.g. overlayfs) report // directory size as 0. if fi.Size() == 0 { return p, os.ErrNotExist From 432fc8a21724cc75e0cd8ec8fd754d2bde42c15f Mon Sep 17 00:00:00 2001 From: box4wangjing Date: Tue, 12 May 2026 01:22:24 +0800 Subject: [PATCH 322/571] refactor: use slices.Backward to simplify the code Signed-off-by: box4wangjing --- internal/chart/v3/util/dependencies.go | 5 +++-- pkg/action/hooks.go | 4 ++-- pkg/chart/v2/util/dependencies.go | 5 +++-- pkg/cmd/history.go | 5 +++-- pkg/repo/v1/index.go | 17 +++++++++-------- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/internal/chart/v3/util/dependencies.go b/internal/chart/v3/util/dependencies.go index 9c4d8e80f..b31f7eb96 100644 --- a/internal/chart/v3/util/dependencies.go +++ b/internal/chart/v3/util/dependencies.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "log/slog" + "slices" "strings" chart "helm.sh/helm/v4/internal/chart/v3" @@ -242,8 +243,8 @@ func set(path []string, data map[string]any) map[string]any { return nil } cur := data - for i := len(path) - 1; i >= 0; i-- { - cur = map[string]any{path[i]: cur} + for _, v := range slices.Backward(path) { + cur = map[string]any{v: cur} } return cur } diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index a4a8da7a6..e7be37bd8 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -150,8 +150,8 @@ func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook return func() error { // If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted // or output should be logged under succeeded condition. If so, then clear the corresponding resource object in each hook - for i := len(executingHooks) - 1; i >= 0; i-- { - h := executingHooks[i] + for _, v := range slices.Backward(executingHooks) { + h := v if err := cfg.outputLogsByPolicy(h, rl.Namespace, release.HookOutputOnSucceeded); err != nil { // We log here as we still want to attempt hook resource deletion even if output logging fails. log.Printf("error outputting logs for hook failure: %v", err) diff --git a/pkg/chart/v2/util/dependencies.go b/pkg/chart/v2/util/dependencies.go index abd673f9d..f28a4f4b1 100644 --- a/pkg/chart/v2/util/dependencies.go +++ b/pkg/chart/v2/util/dependencies.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "log/slog" + "slices" "strings" "helm.sh/helm/v4/internal/copystructure" @@ -242,8 +243,8 @@ func set(path []string, data map[string]any) map[string]any { return nil } cur := data - for i := len(path) - 1; i >= 0; i-- { - cur = map[string]any{path[i]: cur} + for _, v := range slices.Backward(path) { + cur = map[string]any{v: cur} } return cur } diff --git a/pkg/cmd/history.go b/pkg/cmd/history.go index 3349b7bc1..7c0f3ecde 100644 --- a/pkg/cmd/history.go +++ b/pkg/cmd/history.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "io" + "slices" "strconv" "time" @@ -207,8 +208,8 @@ func getHistory(client *action.History, name string) (releaseHistory, error) { } func getReleaseHistory(rls []*release.Release) (history releaseHistory) { - for i := len(rls) - 1; i >= 0; i-- { - r := rls[i] + for _, v := range slices.Backward(rls) { + r := v c := formatChartName(r.Chart) s := r.Info.Status.String() v := r.Version diff --git a/pkg/repo/v1/index.go b/pkg/repo/v1/index.go index 3dbdf7dfc..825dc2753 100644 --- a/pkg/repo/v1/index.go +++ b/pkg/repo/v1/index.go @@ -25,6 +25,7 @@ import ( "os" "path" "path/filepath" + "slices" "sort" "strings" "time" @@ -356,21 +357,21 @@ func loadIndex(data []byte, source string) (*IndexFile, error) { } for name, cvs := range i.Entries { - for idx := len(cvs) - 1; idx >= 0; idx-- { - if cvs[idx] == nil { + for idx, v := range slices.Backward(cvs) { + if v == nil { slog.Warn(fmt.Sprintf("skipping loading invalid entry for chart %q from %s: empty entry", name, source)) cvs = append(cvs[:idx], cvs[idx+1:]...) continue } // When metadata section missing, initialize with no data - if cvs[idx].Metadata == nil { - cvs[idx].Metadata = &chart.Metadata{} + if v.Metadata == nil { + v.Metadata = &chart.Metadata{} } - if cvs[idx].APIVersion == "" { - cvs[idx].APIVersion = chart.APIVersionV1 + if v.APIVersion == "" { + v.APIVersion = chart.APIVersionV1 } - if err := cvs[idx].Validate(); ignoreSkippableChartValidationError(err) != nil { - slog.Warn(fmt.Sprintf("skipping loading invalid entry for chart %q %q from %s: %s", name, cvs[idx].Version, source, err)) + if err := v.Validate(); ignoreSkippableChartValidationError(err) != nil { + slog.Warn(fmt.Sprintf("skipping loading invalid entry for chart %q %q from %s: %s", name, v.Version, source, err)) cvs = append(cvs[:idx], cvs[idx+1:]...) } } From 2032ec5cbaf168de0fadc59959319e4b580ff72e Mon Sep 17 00:00:00 2001 From: Kai Tanaka <275430420+quyentonndbs@users.noreply.github.com> Date: Tue, 12 May 2026 14:12:03 +0000 Subject: [PATCH 323/571] docs: fix 'than'->'that' typo in deprecatedAPIError godoc Signed-off-by: Kai Tanaka <275430420+quyentonndbs@users.noreply.github.com> --- internal/chart/v3/lint/rules/deprecations.go | 2 +- pkg/chart/v2/lint/rules/deprecations.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/chart/v3/lint/rules/deprecations.go b/internal/chart/v3/lint/rules/deprecations.go index a607a5fb4..9c73268af 100644 --- a/internal/chart/v3/lint/rules/deprecations.go +++ b/internal/chart/v3/lint/rules/deprecations.go @@ -28,7 +28,7 @@ import ( kscheme "k8s.io/client-go/kubernetes/scheme" ) -// deprecatedAPIError indicates than an API is deprecated in Kubernetes +// deprecatedAPIError indicates that an API is deprecated in Kubernetes type deprecatedAPIError struct { Deprecated string Message string diff --git a/pkg/chart/v2/lint/rules/deprecations.go b/pkg/chart/v2/lint/rules/deprecations.go index 7d5245869..76626659b 100644 --- a/pkg/chart/v2/lint/rules/deprecations.go +++ b/pkg/chart/v2/lint/rules/deprecations.go @@ -28,7 +28,7 @@ import ( kscheme "k8s.io/client-go/kubernetes/scheme" ) -// deprecatedAPIError indicates than an API is deprecated in Kubernetes +// deprecatedAPIError indicates that an API is deprecated in Kubernetes type deprecatedAPIError struct { Deprecated string Message string From ea2343ebeaf7b4b6b115fd1c7b34a159d6a573ff Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Fri, 15 May 2026 07:16:39 -0700 Subject: [PATCH 324/571] fix(repo): use structured slog args in index.go slog.Error on line 157 passes printf-style positional args (%q, %s) instead of key-value pairs. The slog API treats these as unkeyed attributes, producing garbled log output. Two nearby slog.Warn calls wrap fmt.Sprintf unnecessarily. Convert all three calls to use proper structured key-value arguments. Signed-off-by: Sebastien Tardif Assisted-by: Grok/grok-4 --- pkg/repo/v1/index.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/repo/v1/index.go b/pkg/repo/v1/index.go index 825dc2753..ba747d702 100644 --- a/pkg/repo/v1/index.go +++ b/pkg/repo/v1/index.go @@ -154,7 +154,7 @@ func (i IndexFile) MustAdd(md *chart.Metadata, filename, baseURL, digest string) // Deprecated: Use index.MustAdd instead. func (i IndexFile) Add(md *chart.Metadata, filename, baseURL, digest string) { if err := i.MustAdd(md, filename, baseURL, digest); err != nil { - slog.Error("skipping loading invalid entry for chart %q %q from %s: %s", md.Name, md.Version, filename, err) + slog.Error("skipping loading invalid entry for chart", "name", md.Name, "version", md.Version, "file", filename, "error", err) } } @@ -359,7 +359,7 @@ func loadIndex(data []byte, source string) (*IndexFile, error) { for name, cvs := range i.Entries { for idx, v := range slices.Backward(cvs) { if v == nil { - slog.Warn(fmt.Sprintf("skipping loading invalid entry for chart %q from %s: empty entry", name, source)) + slog.Warn("skipping loading invalid entry for chart: empty entry", "name", name, "source", source) cvs = append(cvs[:idx], cvs[idx+1:]...) continue } @@ -371,7 +371,7 @@ func loadIndex(data []byte, source string) (*IndexFile, error) { v.APIVersion = chart.APIVersionV1 } if err := v.Validate(); ignoreSkippableChartValidationError(err) != nil { - slog.Warn(fmt.Sprintf("skipping loading invalid entry for chart %q %q from %s: %s", name, v.Version, source, err)) + slog.Warn("skipping loading invalid entry for chart", "name", name, "version", v.Version, "source", source, "error", err) cvs = append(cvs[:idx], cvs[idx+1:]...) } } From f772ffedc6154f82bc0ac680f9ffa46054112ac0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 21:33:32 +0000 Subject: [PATCH 325/571] chore(deps): bump github/codeql-action from 4.35.4 to 4.35.5 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.4 to 4.35.5. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/68bde559dea0fdcac2102bfdf6230c5f70eb485e...9e0d7b8d25671d64c341c19c0152d693099fb5ba) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.35.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 256d26cc3..2af5c67cc 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # pinv4.35.4 + uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # pinv4.35.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@68bde559dea0fdcac2102bfdf6230c5f70eb485e # pinv4.35.4 + uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # pinv4.35.5 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # pinv4.35.4 + uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # pinv4.35.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 16a9a8c13..77f220426 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 + uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 with: sarif_file: results.sarif From b5a9299eecefe45da13a01188cabac6af4c336d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 21:34:27 +0000 Subject: [PATCH 326/571] chore(deps): bump github.com/fluxcd/cli-utils from 1.2.0 to 1.2.1 Bumps [github.com/fluxcd/cli-utils](https://github.com/fluxcd/cli-utils) from 1.2.0 to 1.2.1. - [Release notes](https://github.com/fluxcd/cli-utils/releases) - [Commits](https://github.com/fluxcd/cli-utils/compare/v1.2.0...v1.2.1) --- updated-dependencies: - dependency-name: github.com/fluxcd/cli-utils dependency-version: 1.2.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 4fd2ecde8..0ae34e4f6 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.19.0 - github.com/fluxcd/cli-utils v1.2.0 + github.com/fluxcd/cli-utils v1.2.1 github.com/foxcpp/go-mockdns v1.2.0 github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.13.0 @@ -48,7 +48,7 @@ require ( k8s.io/klog/v2 v2.140.0 k8s.io/kubectl v0.36.0 oras.land/oras-go/v2 v2.6.0 - sigs.k8s.io/controller-runtime v0.24.0 + sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/kustomize/kyaml v0.21.1 sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index 50373d1f4..dc6c788e1 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/cli-utils v1.2.0 h1:1o07pXTMxJ/XJ1GpAbLtjdXwfCUMq4Ku1OcnvJHLohI= -github.com/fluxcd/cli-utils v1.2.0/go.mod h1:d5HdTDdR5sCbsIbgtOQ7x7srKYwYeZORU6CD2yn4j/M= +github.com/fluxcd/cli-utils v1.2.1 h1:ug9CicKW7H9QXnvNDapTSKuryZvWcu4Nw7pRvQa6jDY= +github.com/fluxcd/cli-utils v1.2.1/go.mod h1:cky6M6eHvTQkoPtsuFYLIgAMYdpTCSLoor4IA6vueSw= github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -509,8 +509,8 @@ k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0x k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= -sigs.k8s.io/controller-runtime v0.24.0 h1:Ck6N2LdS8Lovy1o25BB4r1xjvLEKUl1s2o9kU+KWDE4= -sigs.k8s.io/controller-runtime v0.24.0/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= +sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= +sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= From 378ceacd9ce239e5b3de1e7ce7c55cf18e16f69b Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Sat, 16 May 2026 15:28:16 +0100 Subject: [PATCH 327/571] fix(upstream): upgrade to cli-utils 1.2.1, controller-runtime 0.24.1 and k8s 1.36.1 Signed-off-by: Matheus Pimenta --- go.mod | 20 ++++++++++---------- go.sum | 40 ++++++++++++++++++++-------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 4fd2ecde8..8220a5f1c 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.19.0 - github.com/fluxcd/cli-utils v1.2.0 + github.com/fluxcd/cli-utils v1.2.1 github.com/foxcpp/go-mockdns v1.2.0 github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.13.0 @@ -39,16 +39,16 @@ require ( golang.org/x/term v0.43.0 golang.org/x/text v0.37.0 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.36.0 - k8s.io/apiextensions-apiserver v0.36.0 - k8s.io/apimachinery v0.36.0 - k8s.io/apiserver v0.36.0 - k8s.io/cli-runtime v0.36.0 - k8s.io/client-go v0.36.0 + k8s.io/api v0.36.1 + k8s.io/apiextensions-apiserver v0.36.1 + k8s.io/apimachinery v0.36.1 + k8s.io/apiserver v0.36.1 + k8s.io/cli-runtime v0.36.1 + k8s.io/client-go v0.36.1 k8s.io/klog/v2 v2.140.0 - k8s.io/kubectl v0.36.0 + k8s.io/kubectl v0.36.1 oras.land/oras-go/v2 v2.6.0 - sigs.k8s.io/controller-runtime v0.24.0 + sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/kustomize/kyaml v0.21.1 sigs.k8s.io/yaml v1.6.0 ) @@ -171,7 +171,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/component-base v0.36.0 // indirect + k8s.io/component-base v0.36.1 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect diff --git a/go.sum b/go.sum index 50373d1f4..544e4205e 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/cli-utils v1.2.0 h1:1o07pXTMxJ/XJ1GpAbLtjdXwfCUMq4Ku1OcnvJHLohI= -github.com/fluxcd/cli-utils v1.2.0/go.mod h1:d5HdTDdR5sCbsIbgtOQ7x7srKYwYeZORU6CD2yn4j/M= +github.com/fluxcd/cli-utils v1.2.1 h1:ug9CicKW7H9QXnvNDapTSKuryZvWcu4Nw7pRvQa6jDY= +github.com/fluxcd/cli-utils v1.2.1/go.mod h1:cky6M6eHvTQkoPtsuFYLIgAMYdpTCSLoor4IA6vueSw= github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -485,32 +485,32 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80= -k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34= -k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0= -k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug= -k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ= -k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc= -k8s.io/apiserver v0.36.0 h1:Jg5OFAENUACByUCg15CmhZAYrr5ZyJ+jodyA1mHl3YE= -k8s.io/apiserver v0.36.0/go.mod h1:mHvwdHf+qKEm+1/hYm756SV+oREOKSPnsjagOpx6Vho= -k8s.io/cli-runtime v0.36.0 h1:HNxciQpQMMOKS0/GiUXcKDyA6J2FDILJj9NmP2BZrTg= -k8s.io/cli-runtime v0.36.0/go.mod h1:KObkknK9Ro5LYX+1RdiKc7C8CvGg4aX+V/Zv+E8WPHA= -k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c= -k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y= -k8s.io/component-base v0.36.0 h1:hFjEktssxiJhrK1zfybkH4kJOi8iZuF+mIDCqS5+jRo= -k8s.io/component-base v0.36.0/go.mod h1:JZvIfcNHk+uck+8LhJzhSBtydWXaZNQwX2OdL+Mnwsk= +k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY= +k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo= +k8s.io/apiextensions-apiserver v0.36.1 h1:6JfYmPUsuUIHuN+3QxutXYWj492RqF5fBSx67GYK5Ks= +k8s.io/apiextensions-apiserver v0.36.1/go.mod h1:pLzZin90riwisdzKwv/GoTwENooytoIx5zWJb4Hkby8= +k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA= +k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8= +k8s.io/apiserver v0.36.1 h1:iMS5V+rPUertv5P9RaqJgmHHTuh4quWpoxchvMUY+JY= +k8s.io/apiserver v0.36.1/go.mod h1:Cby1PbLWztu0GDOxoO6iFOyyqIsziHNEW+w9zVQ22Kw= +k8s.io/cli-runtime v0.36.1 h1:yuC/BGnnj1YYPh6D1P+pZnzinCs6DvMq86yAeNqoqzM= +k8s.io/cli-runtime v0.36.1/go.mod h1:ZQWHGt8xAF7KnviB79vX0lYNyUUqKIpU+LQg7exuFAw= +k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0= +k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU= +k8s.io/component-base v0.36.1 h1:iG6GsELftXqTNG9HG6kiVjatSgAw1sf5pJ6R5a6N0kA= +k8s.io/component-base v0.36.1/go.mod h1:nf9XPlntRdqO6WMeEWAA5F93Y4ICZQdeT9GeqLDB3JI= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/kubectl v0.36.0 h1:hEGr8NvIm2Wjqs2Xy48Uzmvo6lpHdGKlLyMvau2gTms= -k8s.io/kubectl v0.36.0/go.mod h1:iDe8aV5BEi45W8k+5n71I2pJ/nwE0PHDu+/2cejzYoo= +k8s.io/kubectl v0.36.1 h1:96HqS9twIdHM0MlJLTwbo14b9kUKPkOzZ4tlRDLv4qI= +k8s.io/kubectl v0.36.1/go.mod h1:/DGPAIewKsFWF9VFgGvkPhao2Ev4SNuE3BioZo8yPbk= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= -sigs.k8s.io/controller-runtime v0.24.0 h1:Ck6N2LdS8Lovy1o25BB4r1xjvLEKUl1s2o9kU+KWDE4= -sigs.k8s.io/controller-runtime v0.24.0/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= +sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= +sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= From 1a1ec6e3cc052f1358ff668eeb439acd3f8e60c1 Mon Sep 17 00:00:00 2001 From: Gregory Man Date: Sun, 17 May 2026 15:11:42 +0300 Subject: [PATCH 328/571] fix: skip non-Pod test hooks in GetPodLogs Before this change, hooks with `helm.sh/hook: test` but Kind != Pod (ConfigMap, Secret, ...) led to `helm test --logs` fail with `unable to get pod `. Signed-off-by: Gregory Man --- pkg/action/release_testing.go | 4 +++ pkg/action/release_testing_test.go | 50 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index 8cb6ce664..5604e8cc4 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -138,6 +138,10 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error { if len(r.Filters[IncludeNameFilter]) > 0 && !slices.Contains(r.Filters[IncludeNameFilter], h.Name) { continue } + + if h.Kind != "Pod" { + continue + } if err := r.getContainerLogs(out, client, h.Name); err != nil { return err } diff --git a/pkg/action/release_testing_test.go b/pkg/action/release_testing_test.go index dcc708548..2ec62caf0 100644 --- a/pkg/action/release_testing_test.go +++ b/pkg/action/release_testing_test.go @@ -65,13 +65,20 @@ func TestReleaseTestingGetPodLogs_FilterEvents(t *testing.T) { hooks := []*release.Hook{ { + Kind: "Pod", Name: "event-1", Events: []release.HookEvent{release.HookTest}, }, { + Kind: "Pod", Name: "event-2", Events: []release.HookEvent{release.HookTest}, }, + { + Kind: "ConfigMap", + Name: "event-3", + Events: []release.HookEvent{release.HookTest}, + }, } out := &bytes.Buffer{} @@ -80,6 +87,25 @@ func TestReleaseTestingGetPodLogs_FilterEvents(t *testing.T) { assert.Empty(t, out.String()) } +func TestReleaseTestingGetPodLogs_ExcludeFilter_SkipsPodHook(t *testing.T) { + config := actionConfigFixture(t) + require.NoError(t, config.Init(cli.New().RESTClientGetter(), "", os.Getenv("HELM_DRIVER"))) + client := NewReleaseTesting(config) + client.Filters[ExcludeNameFilter] = []string{"excluded-pod"} + + hooks := []*release.Hook{ + { + Kind: "Pod", + Name: "excluded-pod", + Events: []release.HookEvent{release.HookTest}, + }, + } + + out := &bytes.Buffer{} + require.NoError(t, client.GetPodLogs(out, &release.Release{Hooks: hooks})) + assert.Empty(t, out.String()) +} + func TestReleaseTestingGetPodLogs_PodRetrievalError(t *testing.T) { config := actionConfigFixture(t) require.NoError(t, config.Init(cli.New().RESTClientGetter(), "", os.Getenv("HELM_DRIVER"))) @@ -87,6 +113,7 @@ func TestReleaseTestingGetPodLogs_PodRetrievalError(t *testing.T) { hooks := []*release.Hook{ { + Kind: "Pod", Name: "event-1", Events: []release.HookEvent{release.HookTest}, }, @@ -95,6 +122,29 @@ func TestReleaseTestingGetPodLogs_PodRetrievalError(t *testing.T) { require.ErrorContains(t, client.GetPodLogs(&bytes.Buffer{}, &release.Release{Hooks: hooks}), "unable to get pod") } +func TestReleaseTestingGetPodLogs_SkipNonPodHooks(t *testing.T) { + config := actionConfigFixture(t) + require.NoError(t, config.Init(cli.New().RESTClientGetter(), "", os.Getenv("HELM_DRIVER"))) + client := NewReleaseTesting(config) + + hooks := []*release.Hook{ + { + Name: "cm-hook", + Kind: "ConfigMap", + Events: []release.HookEvent{release.HookTest}, + }, + { + Name: "secret-hook", + Kind: "Secret", + Events: []release.HookEvent{release.HookTest}, + }, + } + + out := &bytes.Buffer{} + require.NoError(t, client.GetPodLogs(out, &release.Release{Hooks: hooks})) + assert.Empty(t, out.String()) +} + func TestReleaseTesting_WaitOptionsPassedDownstream(t *testing.T) { is := assert.New(t) config := actionConfigFixture(t) From d097d882d473b42c7460d0be674138a3b52c08da Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Tue, 19 May 2026 19:17:35 +0200 Subject: [PATCH 329/571] Renamed flag from show-rollback to show-rollback-revision Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/cmd/history.go | 6 +++--- pkg/cmd/history_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/cmd/history.go b/pkg/cmd/history.go index bb713ae33..e390cafc4 100644 --- a/pkg/cmd/history.go +++ b/pkg/cmd/history.go @@ -50,9 +50,9 @@ The historical release set is printed as a formatted table, e.g: 3 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Rolled back to 2 4 Mon Oct 3 10:15:13 2016 deployed alpine-0.1.0 1.0 Upgraded successfully -Use '--show-rollback' to include a column showing the revision that was rolled back to: +Use '--show-rollback-revision' to include a column showing the revision that was rolled back to: - $ helm history angry-bird --show-rollback + $ helm history angry-bird --show-rollback-revision REVISION UPDATED STATUS CHART APP VERSION ROLLBACK DESCRIPTION 1 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Initial install 2 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Upgraded successfully @@ -92,7 +92,7 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f := cmd.Flags() f.IntVar(&client.Max, "max", 256, "maximum number of revision to include in history") - f.BoolVar(&showRollback, "show-rollback", false, "show the rollback revision column in table output") + f.BoolVar(&showRollback, "show-rollback-revision", false, "show the rollback revision column in table output") bindOutputFlag(cmd, &outfmt) return cmd diff --git a/pkg/cmd/history_test.go b/pkg/cmd/history_test.go index b25eaba7f..3ec59a99f 100644 --- a/pkg/cmd/history_test.go +++ b/pkg/cmd/history_test.go @@ -130,8 +130,8 @@ func TestHistoryWithRollback(t *testing.T) { rels: rels, golden: "output/history-with-rollback-no-flag.txt", }, { - name: "history with rollback revision and --show-rollback flag", - cmd: "history angry-bird --show-rollback", + name: "history with rollback revision and --show-rollback-revision flag", + cmd: "history angry-bird --show-rollback-revision", rels: rels, golden: "output/history-with-rollback.txt", }, { From 979e68fbbd5a932c67b756884880d7641f268632 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 20 May 2026 13:24:39 +0300 Subject: [PATCH 330/571] internal/plugin: remove zero-width spaces from plugin name comment The comment describing allowed plugin name characters contained three U+200B (zero-width space) characters around the '_' and '-' literals. The rendered comment is identical without them; the ZWSP were likely copy-pasted from a rich-text source. Downstream impact: every project that vendors helm.sh/helm/v4 and runs Renovate gets a repo-wide warning on its Dependency Dashboard about hidden Unicode characters. Renovate scans the whole tree (including vendor/) for ZWSP/bidi-override codepoints and cannot be told to skip a path for this specific check. Removing the characters here clears the warning everywhere downstream. Signed-off-by: Aleksei Sviridkin --- internal/plugin/plugin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/plugin/plugin.go b/internal/plugin/plugin.go index 132b1739e..789e583ae 100644 --- a/internal/plugin/plugin.go +++ b/internal/plugin/plugin.go @@ -77,5 +77,5 @@ type Output struct { // validPluginName is a regular expression that validates plugin names. // -// Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, ​_​ and ​-. +// Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, _ and -. var validPluginName = regexp.MustCompile("^[A-Za-z0-9_-]+$") From e095e690a8b3c31b6dbcb9af02f870cc41fbf18b Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Thu, 21 May 2026 07:28:33 -0700 Subject: [PATCH 331/571] fix(provenance): check error return in Digest Return the actual error from io.Copy in Digest() instead of nil. The previous code swallowed the error and returned an empty string as a valid SHA-256 digest, which could silently break chart provenance verification. Also fix encodeRelease() in pkg/storage/driver/util.go: - Close the gzip writer on the w.Write() error path to avoid leaking resources. - Check the error return from gzip.Writer.Close(), which flushes remaining compressed data and can fail. Assisted-by: Grok/xAI Signed-off-by: Sebastien Tardif --- pkg/provenance/sign.go | 2 +- pkg/storage/driver/util.go | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/provenance/sign.go b/pkg/provenance/sign.go index 45d4fe1a5..9b3e774cb 100644 --- a/pkg/provenance/sign.go +++ b/pkg/provenance/sign.go @@ -388,7 +388,7 @@ func DigestFile(filename string) (string, error) { func Digest(in io.Reader) (string, error) { hash := crypto.SHA256.New() if _, err := io.Copy(hash, in); err != nil { - return "", nil + return "", err } return hex.EncodeToString(hash.Sum(nil)), nil } diff --git a/pkg/storage/driver/util.go b/pkg/storage/driver/util.go index ca8e23cc2..756731ef6 100644 --- a/pkg/storage/driver/util.go +++ b/pkg/storage/driver/util.go @@ -46,9 +46,12 @@ func encodeRelease(rls *rspb.Release) (string, error) { return "", err } if _, err = w.Write(b); err != nil { + w.Close() + return "", err + } + if err = w.Close(); err != nil { return "", err } - w.Close() return b64.EncodeToString(buf.Bytes()), nil } From ace245b8273e23c9f12993befa535a19ab1bc172 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 21:33:33 +0000 Subject: [PATCH 332/571] chore(deps): bump actions/stale from 10.2.0 to 10.3.0 Bumps [actions/stale](https://github.com/actions/stale) from 10.2.0 to 10.3.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/b5d41d4e1d5dceea10e7104786b73624c18a190f...eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899) --- updated-dependencies: - dependency-name: actions/stale dependency-version: 10.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/stale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 9b0c29952..bbe339e79 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -12,7 +12,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 + - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.' From 442e1460b9ec4450a7c12d6b6a59848e7b16d9d1 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Fri, 22 May 2026 09:47:08 +0200 Subject: [PATCH 333/571] docs: update version status for v4 stable release Helm v4 shipped on 2025-11-17. Mark it as the current stable release and v3 as support-mode with its end-of-support dates. Signed-off-by: Benoit Tigeot --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 37bc8abaa..d581ceac3 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,8 @@ Think of it like apt/yum/homebrew for Kubernetes. ## Helm Development and Stable Versions -Helm v4 is currently under development on the `main` branch. This is unstable and the APIs within the Go SDK and at the command line are changing. -Helm v3 (current stable) is maintained on the `dev-v3` branch. APIs there follow semantic versioning. +Helm v4 is the current stable release, developed on the `main` branch. +Helm v3 is in support mode on the `dev-v3` branch: bug fixes until July 8th 2026, security fixes until November 11th 2026. ## Install @@ -64,7 +64,7 @@ Get started with the [Quick Start guide](https://helm.sh/docs/intro/quickstart/) The [Helm roadmap uses GitHub milestones](https://github.com/helm/helm/milestones) to track the progress of the project. -The development of Helm v4 is currently happening on the `main` branch while the development of Helm v3, the stable branch, is happening on the `dev-v3` branch. Changes should be made to the `main` branch prior to being added to the `dev-v3` branch so that all changes are carried along to Helm v4. +Helm v4 development happens on the `main` branch. Helm v3 is in support mode on the `dev-v3` branch and receives only bug and security fixes. ## Community, discussion, contribution, and support From 60665e9035ccab375780ef77934bde2ffa674448 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 21:33:30 +0000 Subject: [PATCH 334/571] chore(deps): bump github/codeql-action from 4.35.5 to 4.36.0 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.5 to 4.36.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/9e0d7b8d25671d64c341c19c0152d693099fb5ba...7211b7c8077ea37d8641b6271f6a365a22a5fbfa) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.36.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 2af5c67cc..1c3ea0bf4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # pinv4.35.5 + uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # pinv4.36.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # pinv4.35.5 + uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # pinv4.36.0 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # pinv4.35.5 + uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # pinv4.36.0 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 77f220426..c4a0cb196 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 + uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 with: sarif_file: results.sarif From dbb3e353372f6e4abca9155c44d8b9a5f1e97626 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 21:33:35 +0000 Subject: [PATCH 335/571] chore(deps): bump golangci/golangci-lint-action from 9.2.0 to 9.2.1 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 9.2.0 to 9.2.1. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/1e7e51e771db61008b38414a730f564565cf7c20...82606bf257cbaff209d206a39f5134f0cfbfd2ee) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-version: 9.2.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/golangci-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 8b64a3be2..734bc682a 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -22,6 +22,6 @@ jobs: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true - name: golangci-lint - uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 #pin@9.2.0 + uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee #pin@9.2.1 with: version: ${{ env.GOLANGCI_LINT_VERSION }} From 54ae27fd841f33fd979476b1c378fe58fe7ff52f Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Fri, 22 May 2026 16:09:20 -0600 Subject: [PATCH 336/571] fix(deps): bump golang.org/x/net to v0.55.0 to address GO-2026-5026 Upgrades golang.org/x/net from v0.53.0 to v0.55.0 to fix CVE-2026-39821 (GO-2026-5026), where idna.ToASCII/ToUnicode incorrectly accept Punycode- encoded labels that decode to ASCII-only labels, enabling privilege escalation via hostname check bypass. Coordinated x/ upgrade pulled in by the module graph: - golang.org/x/sys v0.44.0 => v0.45.0 Signed-off-by: Terry Howe --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 8220a5f1c..14662ddb4 100644 --- a/go.mod +++ b/go.mod @@ -158,10 +158,10 @@ require ( go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/mod v0.35.0 // indirect - golang.org/x/net v0.53.0 // indirect + golang.org/x/net v0.55.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.44.0 // indirect + golang.org/x/sys v0.45.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.44.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect diff --git a/go.sum b/go.sum index 544e4205e..422a39eb9 100644 --- a/go.sum +++ b/go.sum @@ -400,8 +400,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -430,8 +430,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= From e679ec9f041f9fa484f8539efd4b5e665526b883 Mon Sep 17 00:00:00 2001 From: Arnav Nagzirkar Date: Mon, 25 May 2026 22:27:12 -0700 Subject: [PATCH 337/571] ci: enable bidichk linter to prevent invisible Unicode characters Adds the bidichk linter to .golangci.yml to detect dangerous invisible Unicode characters (ZWSP, bidi controls) in source files during CI. Fixes #32137 Signed-off-by: Arnav Nagzirkar --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index 1ed3353b4..ca3566f15 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,6 +19,7 @@ linters: # Keep sorted alphabetically enable: + - bidichk - depguard - dupl - exhaustive From 7510b832149ee739b3ace110f8238e1ffd922cca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 May 2026 02:05:35 +0000 Subject: [PATCH 338/571] chore(deps): bump golang.org/x/crypto from 0.51.0 to 0.52.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.51.0 to 0.52.0. - [Commits](https://github.com/golang/crypto/compare/v0.51.0...v0.52.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.52.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 14662ddb4..2cd535e49 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/tetratelabs/wazero v1.11.0 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.51.0 + golang.org/x/crypto v0.52.0 golang.org/x/term v0.43.0 golang.org/x/text v0.37.0 gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 422a39eb9..30244c99c 100644 --- a/go.sum +++ b/go.sum @@ -382,8 +382,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= -golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= From 6dc1c1ccf8066b06f5e30c5bf1d2cdd2924c8c43 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 27 May 2026 14:59:21 -0400 Subject: [PATCH 339/571] lower resync period Signed-off-by: Austin Abro --- pkg/kube/statuswait.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index 59c1218ff..29de0af2b 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -68,6 +68,12 @@ func alwaysReady(_ *unstructured.Unstructured) (*status.Result, error) { }, nil } +func getStatusWatcher(dynamicClient dynamic.Interface, mapper meta.RESTMapper) *watcher.DefaultStatusWatcher { + sw := watcher.NewDefaultStatusWatcher(dynamicClient, mapper) + sw.ResyncPeriod = 3 * time.Minute + return sw +} + func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.Duration) error { if timeout == 0 { timeout = DefaultStatusWatcherTimeout @@ -75,7 +81,7 @@ func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.D ctx, cancel := w.contextWithTimeout(w.watchUntilReadyCtx, timeout) defer cancel() w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) - sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) + sw := getStatusWatcher(w.client, w.restMapper) jobSR := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) podSR := helmStatusReaders.NewCustomPodStatusReader(w.restMapper) // We don't want to wait on any other resources as watchUntilReady is only for Helm hooks. @@ -97,7 +103,7 @@ func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) er ctx, cancel := w.contextWithTimeout(w.waitCtx, timeout) defer cancel() w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) - sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) + sw := getStatusWatcher(w.client, w.restMapper) sw.StatusReader = statusreaders.NewStatusReader(w.restMapper, w.readers...) return w.wait(ctx, resourceList, sw) } @@ -109,7 +115,7 @@ func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Dura ctx, cancel := w.contextWithTimeout(w.waitWithJobsCtx, timeout) defer cancel() w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) - sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) + sw := getStatusWatcher(w.client, w.restMapper) newCustomJobStatusReader := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) readers := append([]engine.StatusReader(nil), w.readers...) readers = append(readers, newCustomJobStatusReader) @@ -125,7 +131,7 @@ func (w *statusWaiter) WaitForDelete(resourceList ResourceList, timeout time.Dur ctx, cancel := w.contextWithTimeout(w.waitForDeleteCtx, timeout) defer cancel() w.Logger().Debug("waiting for resources to be deleted", "count", len(resourceList), "timeout", timeout) - sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) + sw := getStatusWatcher(w.client, w.restMapper) return w.waitForDelete(ctx, resourceList, sw) } From 488c4a805debe0fc1b2777fa1823e079e79a5869 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 29 May 2026 08:01:54 +0200 Subject: [PATCH 340/571] Fix empty and len testifylint violations across test files Signed-off-by: Matthieu MOREL --- .golangci.yml | 2 -- internal/chart/v3/chart_test.go | 2 +- internal/chart/v3/lint/rules/crds_test.go | 2 +- internal/plugin/loader_test.go | 2 +- pkg/action/action_test.go | 10 +++++----- pkg/action/get_metadata_test.go | 6 +++--- pkg/action/install_test.go | 14 +++++++------- pkg/action/list_test.go | 4 ++-- pkg/action/upgrade_test.go | 2 +- pkg/chart/v2/chart_test.go | 2 +- pkg/chart/v2/lint/rules/crds_test.go | 2 +- pkg/engine/files_test.go | 2 +- pkg/kube/client_test.go | 4 ++-- pkg/registry/registry_test.go | 2 +- 14 files changed, 27 insertions(+), 29 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index b7afe1f8e..01bd24569 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -99,13 +99,11 @@ linters: testifylint: disable: - - empty - encoded-compare - equal-values - expected-actual - float-compare - go-require - - len - nil-compare - require-error - suite-dont-use-pkg diff --git a/internal/chart/v3/chart_test.go b/internal/chart/v3/chart_test.go index f89030095..d5d066d9b 100644 --- a/internal/chart/v3/chart_test.go +++ b/internal/chart/v3/chart_test.go @@ -59,7 +59,7 @@ func TestCRDs(t *testing.T) { is := assert.New(t) crds := chrt.CRDs() - is.Equal(2, len(crds)) + is.Len(crds, 2) is.Equal("crds/foo.yaml", crds[0].Name) is.Equal("crds/foo/bar/baz.yaml", crds[1].Name) } diff --git a/internal/chart/v3/lint/rules/crds_test.go b/internal/chart/v3/lint/rules/crds_test.go index e435b8ea3..b80339d7b 100644 --- a/internal/chart/v3/lint/rules/crds_test.go +++ b/internal/chart/v3/lint/rules/crds_test.go @@ -62,5 +62,5 @@ metadata: linter := support.Linter{ChartDir: chartDir} Crds(&linter) - assert.Len(t, linter.Messages, 0) + assert.Empty(t, linter.Messages) } diff --git a/internal/plugin/loader_test.go b/internal/plugin/loader_test.go index b98f87a07..d2d50f7df 100644 --- a/internal/plugin/loader_test.go +++ b/internal/plugin/loader_test.go @@ -208,7 +208,7 @@ func TestLoadAllDir_Empty(t *testing.T) { emptyDir := t.TempDir() plugs, err := LoadAllDir(emptyDir, func(_ string, err error) error { return err }) require.NoError(t, err) - assert.Len(t, plugs, 0) + assert.Empty(t, plugs) } func TestLoadAllPluginsDir(t *testing.T) { diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index 54b07273b..6a561985f 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -1742,7 +1742,7 @@ data: assert.Contains(t, err.Error(), tt.expectedError) } else { assert.NoError(t, err) - assert.Equal(t, len(tt.expectedFiles), len(files)) + assert.Len(t, files, len(tt.expectedFiles)) for expectedFile, expectedContent := range tt.expectedFiles { actualContent, exists := files[expectedFile] @@ -1793,7 +1793,7 @@ data: require.NoError(t, err) // Compare the results - assert.Equal(t, len(originalFiles), len(reconstructed)) + assert.Len(t, reconstructed, len(originalFiles)) for filename, originalContent := range originalFiles { reconstructedContent, exists := reconstructed[filename] assert.True(t, exists, "File %s should exist in reconstructed files", filename) @@ -1830,7 +1830,7 @@ func TestRenderResources_PostRenderer_Success(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, hooks) assert.NotNil(t, buf) - assert.Equal(t, "", notes) + assert.Empty(t, notes) expectedBuf := `--- # Source: yellow/templates/foodpie foodpie: world @@ -1948,7 +1948,7 @@ func TestRenderResources_PostRenderer_Integration(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, hooks) assert.NotNil(t, buf) - assert.Equal(t, "", notes) // Notes should be empty for this test + assert.Empty(t, notes) // Notes should be empty for this test // Verify that the post-renderer modifications are present in the output output := buf.String() @@ -1987,7 +1987,7 @@ func TestRenderResources_NoPostRenderer(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, hooks) assert.NotNil(t, buf) - assert.Equal(t, "", notes) + assert.Empty(t, notes) } func TestRenderResources_PostRenderer_DuplicateResourceInHookAndTemplate(t *testing.T) { diff --git a/pkg/action/get_metadata_test.go b/pkg/action/get_metadata_test.go index 4caa966ab..b488abf64 100644 --- a/pkg/action/get_metadata_test.go +++ b/pkg/action/get_metadata_test.go @@ -279,9 +279,9 @@ func TestGetMetadata_Run_WithMixedDependencies(t *testing.T) { // Verify dependencies without aliases assert.Equal(t, "nginx", dep1.Name()) - assert.Equal(t, "", dep1.Alias()) + assert.Empty(t, dep1.Alias()) assert.Equal(t, "postgresql", dep3.Name()) - assert.Equal(t, "", dep3.Alias()) + assert.Empty(t, dep3.Alias()) } func TestGetMetadata_Run_WithAnnotations(t *testing.T) { @@ -485,7 +485,7 @@ func TestGetMetadata_Run_EmptyAppVersion(t *testing.T) { result, err := client.Run(releaseName) require.NoError(t, err) - assert.Equal(t, "", result.AppVersion) + assert.Empty(t, result.AppVersion) } func TestMetadata_FormattedDepNames(t *testing.T) { diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 38d692a0b..fa63193bc 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -204,8 +204,8 @@ func TestInstallRelease(t *testing.T) { is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall) is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete") - is.NotEqual(len(res.Manifest), 0) - is.NotEqual(len(rel.Manifest), 0) + is.NotEmpty(res.Manifest) + is.NotEmpty(rel.Manifest) is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") is.Equal(rel.Info.Description, "Install complete") @@ -318,8 +318,8 @@ func TestInstallReleaseWithValues(t *testing.T) { is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall) is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete") - is.NotEqual(len(res.Manifest), 0) - is.NotEqual(len(rel.Manifest), 0) + is.NotEmpty(res.Manifest) + is.NotEmpty(rel.Manifest) is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") is.Equal("Install complete", rel.Info.Description) is.Equal(expectedUserValues, rel.Config) @@ -359,8 +359,8 @@ func TestInstallRelease_WithNotes(t *testing.T) { is.Equal(rel.Hooks[0].Manifest, manifestWithHook) is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall) is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete") - is.NotEqual(len(res.Manifest), 0) - is.NotEqual(len(rel.Manifest), 0) + is.NotEmpty(res.Manifest) + is.NotEmpty(rel.Manifest) is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") is.Equal(rel.Info.Description, "Install complete") @@ -582,7 +582,7 @@ func TestInstallRelease_FailedHooks(t *testing.T) { res, err := releaserToV1Release(resi) is.NoError(err) is.Contains(res.Info.Description, "failed post-install") - is.Equal("", outBuffer.String()) + is.Empty(outBuffer.String()) is.Equal(rcommon.StatusFailed, res.Info.Status) } diff --git a/pkg/action/list_test.go b/pkg/action/list_test.go index 643bcea42..19596dbbb 100644 --- a/pkg/action/list_test.go +++ b/pkg/action/list_test.go @@ -65,7 +65,7 @@ func TestList_Empty(t *testing.T) { lister := NewList(actionConfigFixture(t)) list, err := lister.Run() assert.NoError(t, err) - assert.Len(t, list, 0) + assert.Empty(t, list) } func newListFixture(t *testing.T) *List { @@ -166,7 +166,7 @@ func TestList_LimitOffsetOutOfBounds(t *testing.T) { makeMeSomeReleases(t, lister.cfg.Releases) list, err := lister.Run() is.NoError(err) - is.Len(list, 0) + is.Empty(list) lister.Limit = 10 lister.Offset = 1 diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 4e48b28a6..719a94720 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -328,7 +328,7 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { is.NoError(err) is.Equal(common.StatusDeployed, updatedRes.Info.Status) - is.Equal(0, len(updatedRes.Chart.Dependencies()), "expected 0 dependencies") + is.Empty(updatedRes.Chart.Dependencies(), "expected 0 dependencies") expectedValues := map[string]any{ "subchart": map[string]any{ diff --git a/pkg/chart/v2/chart_test.go b/pkg/chart/v2/chart_test.go index 52e9c6b43..813669366 100644 --- a/pkg/chart/v2/chart_test.go +++ b/pkg/chart/v2/chart_test.go @@ -59,7 +59,7 @@ func TestCRDs(t *testing.T) { is := assert.New(t) crds := chrt.CRDs() - is.Equal(2, len(crds)) + is.Len(crds, 2) is.Equal("crds/foo.yaml", crds[0].Name) is.Equal("crds/foo/bar/baz.yaml", crds[1].Name) } diff --git a/pkg/chart/v2/lint/rules/crds_test.go b/pkg/chart/v2/lint/rules/crds_test.go index 228f40a66..759bb54fa 100644 --- a/pkg/chart/v2/lint/rules/crds_test.go +++ b/pkg/chart/v2/lint/rules/crds_test.go @@ -62,5 +62,5 @@ metadata: linter := support.Linter{ChartDir: chartDir} Crds(&linter) - assert.Len(t, linter.Messages, 0) + assert.Empty(t, linter.Messages) } diff --git a/pkg/engine/files_test.go b/pkg/engine/files_test.go index e53263c76..f07f6afeb 100644 --- a/pkg/engine/files_test.go +++ b/pkg/engine/files_test.go @@ -107,5 +107,5 @@ func TestBlankLines(t *testing.T) { as.Len(out, 4) as.Equal("bar", out[0]) - as.Equal("", out[3]) + as.Empty(out[3]) } diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index ed871c05a..5f85f5d09 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -550,9 +550,9 @@ func TestUpdate(t *testing.T) { // Special handling for the rollback test case if name == "rollback after failed upgrade with removed resource" { - assert.Len(t, result.Created, 0, "expected 0 resource created, got %d", len(result.Created)) + assert.Empty(t, result.Created, "expected 0 resource created, got %d", len(result.Created)) assert.Len(t, result.Updated, 1, "expected 1 resource updated, got %d", len(result.Updated)) - assert.Len(t, result.Deleted, 0, "expected 0 resource deleted, got %d", len(result.Deleted)) + assert.Empty(t, result.Deleted, "expected 0 resource deleted, got %d", len(result.Deleted)) } else { assert.Len(t, result.Created, 1, "expected 1 resource created, got %d", len(result.Created)) assert.Len(t, result.Updated, 2, "expected 2 resource updated, got %d", len(result.Updated)) diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index 9b9f51ca0..8a4296591 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -556,5 +556,5 @@ func testTags(suite *TestRegistry) { // Query for tags and validate length tags, err := suite.RegistryClient.Tags(ref) suite.Require().NoError(err, "no error retrieving tags") - suite.Equal(1, len(tags)) + suite.Len(tags, 1) } From 3aa1b742b9228219378ce3d1b978d8d9e30547cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 May 2026 23:30:01 +0000 Subject: [PATCH 341/571] chore(deps): bump github.com/tetratelabs/wazero from 1.11.0 to 1.12.0 Bumps [github.com/tetratelabs/wazero](https://github.com/tetratelabs/wazero) from 1.11.0 to 1.12.0. - [Release notes](https://github.com/tetratelabs/wazero/releases) - [Commits](https://github.com/tetratelabs/wazero/compare/v1.11.0...v1.12.0) --- updated-dependencies: - dependency-name: github.com/tetratelabs/wazero dependency-version: 1.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2cd535e49..77d0e92fc 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 - github.com/tetratelabs/wazero v1.11.0 + github.com/tetratelabs/wazero v1.12.0 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.52.0 golang.org/x/term v0.43.0 diff --git a/go.sum b/go.sum index 30244c99c..a48486be0 100644 --- a/go.sum +++ b/go.sum @@ -309,8 +309,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q= github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk= -github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA= -github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU= +github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU= +github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= From f8abbfd7d4ab7d9cd883fab5d24c8175eaec56b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 09:57:37 +0000 Subject: [PATCH 342/571] chore(deps): bump github/codeql-action from 4.36.0 to 4.36.1 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.36.0 to 4.36.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/7211b7c8077ea37d8641b6271f6a365a22a5fbfa...87557b9c84dde89fdd9b10e88954ac2f4248e463) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.36.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1c3ea0bf4..351acf713 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # pinv4.36.0 + uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # pinv4.36.1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # pinv4.36.0 + uses: github/codeql-action/autobuild@87557b9c84dde89fdd9b10e88954ac2f4248e463 # pinv4.36.1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # pinv4.36.0 + uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # pinv4.36.1 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index c4a0cb196..0fe504b87 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 + uses: github/codeql-action/upload-sarif@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4.36.1 with: sarif_file: results.sarif From c603c50aa654b23c46c7e248cdf5cd016f0a3fa8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 21:32:47 +0000 Subject: [PATCH 343/571] chore(deps): bump github/codeql-action from 4.36.1 to 4.36.2 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.36.1 to 4.36.2. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/87557b9c84dde89fdd9b10e88954ac2f4248e463...8aad20d150bbac5944a9f9d289da16a4b0d87c1e) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.36.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 351acf713..008d6a594 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # pinv4.36.1 + uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # pinv4.36.2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@87557b9c84dde89fdd9b10e88954ac2f4248e463 # pinv4.36.1 + uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # pinv4.36.2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # pinv4.36.1 + uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # pinv4.36.2 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 0fe504b87..55a9aec82 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4.36.1 + uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 with: sarif_file: results.sarif From 7f855dfe8a1176d41808451aa0e8ab7b11664f03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 21:33:52 +0000 Subject: [PATCH 344/571] chore(deps): bump golang.org/x/text from 0.37.0 to 0.38.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.37.0 to 0.38.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.37.0...v0.38.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.38.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 77d0e92fc..4f317e079 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.52.0 golang.org/x/term v0.43.0 - golang.org/x/text v0.37.0 + golang.org/x/text v0.38.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.36.1 k8s.io/apiextensions-apiserver v0.36.1 @@ -157,13 +157,13 @@ require ( go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/mod v0.35.0 // indirect + golang.org/x/mod v0.36.0 // indirect golang.org/x/net v0.55.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.20.0 // indirect + golang.org/x/sync v0.21.0 // indirect golang.org/x/sys v0.45.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.44.0 // indirect + golang.org/x/tools v0.45.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/grpc v1.80.0 // indirect diff --git a/go.sum b/go.sum index a48486be0..dbc195cc9 100644 --- a/go.sum +++ b/go.sum @@ -388,8 +388,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -412,8 +412,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -448,8 +448,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -458,8 +458,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= From 1019146bb36fde225ee93fdb173d12c91d96b834 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 10:34:10 +0000 Subject: [PATCH 345/571] chore(deps): bump golang.org/x/term from 0.43.0 to 0.44.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.43.0 to 0.44.0. - [Commits](https://github.com/golang/term/compare/v0.43.0...v0.44.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-version: 0.44.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 4f317e079..9629f9c00 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/tetratelabs/wazero v1.12.0 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.52.0 - golang.org/x/term v0.43.0 + golang.org/x/term v0.44.0 golang.org/x/text v0.38.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.36.1 @@ -161,7 +161,7 @@ require ( golang.org/x/net v0.55.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.21.0 // indirect - golang.org/x/sys v0.45.0 // indirect + golang.org/x/sys v0.46.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.45.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect diff --git a/go.sum b/go.sum index dbc195cc9..725fcc333 100644 --- a/go.sum +++ b/go.sum @@ -430,8 +430,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -439,8 +439,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= From 33b40718860fcb563ef34d0a4af2a1e565fdd108 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 10:59:28 +0000 Subject: [PATCH 346/571] chore(deps): bump golang.org/x/crypto from 0.52.0 to 0.53.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.52.0 to 0.53.0. - [Commits](https://github.com/golang/crypto/compare/v0.52.0...v0.53.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.53.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9629f9c00..db1ebd0a4 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/tetratelabs/wazero v1.12.0 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.52.0 + golang.org/x/crypto v0.53.0 golang.org/x/term v0.44.0 golang.org/x/text v0.38.0 gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 725fcc333..54105cad2 100644 --- a/go.sum +++ b/go.sum @@ -382,8 +382,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= From 74c1702157722e44f72c4a731c652c9e6ed58f83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 21:33:08 +0000 Subject: [PATCH 347/571] chore(deps): bump oras.land/oras-go/v2 from 2.6.0 to 2.6.1 Bumps [oras.land/oras-go/v2](https://github.com/oras-project/oras-go) from 2.6.0 to 2.6.1. - [Release notes](https://github.com/oras-project/oras-go/releases) - [Commits](https://github.com/oras-project/oras-go/compare/v2.6.0...v2.6.1) --- updated-dependencies: - dependency-name: oras.land/oras-go/v2 dependency-version: 2.6.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9629f9c00..02ce7d963 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( k8s.io/client-go v0.36.1 k8s.io/klog/v2 v2.140.0 k8s.io/kubectl v0.36.1 - oras.land/oras-go/v2 v2.6.0 + oras.land/oras-go/v2 v2.6.1 sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/kustomize/kyaml v0.21.1 sigs.k8s.io/yaml v1.6.0 diff --git a/go.sum b/go.sum index 725fcc333..153c6dcdb 100644 --- a/go.sum +++ b/go.sum @@ -507,8 +507,8 @@ k8s.io/kubectl v0.36.1 h1:96HqS9twIdHM0MlJLTwbo14b9kUKPkOzZ4tlRDLv4qI= k8s.io/kubectl v0.36.1/go.mod h1:/DGPAIewKsFWF9VFgGvkPhao2Ev4SNuE3BioZo8yPbk= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= -oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= +oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= +oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk= sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= From 7058f841af78112f81098e3f434f90d30c43b4fd Mon Sep 17 00:00:00 2001 From: Ogulcan Aydogan Date: Thu, 11 Jun 2026 22:31:18 +0100 Subject: [PATCH 348/571] fix(engine): add debug logging when lookup returns empty (#32205) When lookup cannot find the requested resource (apierrors.IsNotFound), add slog.Debug() calls with structured fields (apiVersion, kind, namespace, name) so that users running helm template --debug can see why lookup returned an empty map instead of silently swallowing the not-found result. Fixes: https://github.com/helm/helm/issues/32101 Signed-off-by: Ogulcan Aydogan --- pkg/engine/lookup_func.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go index 52b6ffdaf..c5c441c14 100644 --- a/pkg/engine/lookup_func.go +++ b/pkg/engine/lookup_func.go @@ -73,6 +73,12 @@ func newLookupFunction(clientProvider ClientProvider) lookupFunc { if apierrors.IsNotFound(err) { // Just return an empty interface when the object was not found. // That way, users can use `if not (lookup ...)` in their templates. + slog.Debug("lookup: resource not found", + slog.String("apiVersion", apiversion), + slog.String("kind", kind), + slog.String("namespace", namespace), + slog.String("name", name), + ) return map[string]any{}, nil } return map[string]any{}, err @@ -85,6 +91,11 @@ func newLookupFunction(clientProvider ClientProvider) lookupFunc { if apierrors.IsNotFound(err) { // Just return an empty interface when the object was not found. // That way, users can use `if not (lookup ...)` in their templates. + slog.Debug("lookup: resource list not found", + slog.String("apiVersion", apiversion), + slog.String("kind", kind), + slog.String("namespace", namespace), + ) return map[string]any{}, nil } return map[string]any{}, err From c2f1b238a114d6d7a85b37f80fc6975bd48e4c09 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Thu, 11 Jun 2026 15:36:42 -0600 Subject: [PATCH 349/571] fix: route registry client output to stdout instead of stderr (#32056) Commands like 'helm registry login', 'helm push', and 'helm pull' were writing success messages ("Login Succeeded", "Pushed:", "Pulled:", "Digest:") to stderr instead of stdout. The root cause was that newDefaultRegistryClient and newRegistryClientWithTLS hard-coded os.Stderr as the registry client writer, ignoring the out io.Writer that main() passes as os.Stdout. Thread out io.Writer through newRegistryClient, newDefaultRegistryClient, and newRegistryClientWithTLS, and update all call sites in pkg/cmd. Fixes #13464 Signed-off-by: Terry Howe --- pkg/cmd/dependency_build.go | 2 +- pkg/cmd/dependency_update.go | 2 +- pkg/cmd/install.go | 2 +- pkg/cmd/package.go | 2 +- pkg/cmd/pull.go | 2 +- pkg/cmd/push.go | 2 +- pkg/cmd/root.go | 16 ++++++++-------- pkg/cmd/show.go | 14 +++++++------- pkg/cmd/template.go | 2 +- pkg/cmd/upgrade.go | 2 +- 10 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pkg/cmd/dependency_build.go b/pkg/cmd/dependency_build.go index b8ac16e60..f572fbae1 100644 --- a/pkg/cmd/dependency_build.go +++ b/pkg/cmd/dependency_build.go @@ -55,7 +55,7 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command { if len(args) > 0 { chartpath = filepath.Clean(args[0]) } - registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/dependency_update.go b/pkg/cmd/dependency_update.go index 7f805c37b..a7131e587 100644 --- a/pkg/cmd/dependency_update.go +++ b/pkg/cmd/dependency_update.go @@ -58,7 +58,7 @@ func newDependencyUpdateCmd(_ *action.Configuration, out io.Writer) *cobra.Comma if len(args) > 0 { chartpath = filepath.Clean(args[0]) } - registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index 67e2a9fab..832968ff0 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -143,7 +143,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return compInstall(args, toComplete, client) }, RunE: func(cmd *cobra.Command, args []string) error { - registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/package.go b/pkg/cmd/package.go index 14f9c8425..46133d971 100644 --- a/pkg/cmd/package.go +++ b/pkg/cmd/package.go @@ -75,7 +75,7 @@ func newPackageCmd(out io.Writer) *cobra.Command { return err } - registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/pull.go b/pkg/cmd/pull.go index bb7a8d1c0..d371db4ad 100644 --- a/pkg/cmd/pull.go +++ b/pkg/cmd/pull.go @@ -65,7 +65,7 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.Version = ">0.0.0-0" } - registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/push.go b/pkg/cmd/push.go index 94c5732ff..f32ce92be 100644 --- a/pkg/cmd/push.go +++ b/pkg/cmd/push.go @@ -71,7 +71,7 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, RunE: func(_ *cobra.Command, args []string) error { registryClient, err := newRegistryClient( - o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSVerify, o.plainHTTP, o.username, o.password, + out, o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSVerify, o.plainHTTP, o.username, o.password, ) if err != nil { diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 1fa01ca0e..3203ced25 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -257,7 +257,7 @@ func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, arg log.Fatal(err) } - registryClient, err := newDefaultRegistryClient(false, "", "") + registryClient, err := newDefaultRegistryClient(out, false, "", "") if err != nil { return nil, err } @@ -401,27 +401,27 @@ func checkForExpiredRepos(repofile string) { } func newRegistryClient( - certFile, keyFile, caFile string, insecureSkipTLSVerify, plainHTTP bool, username, password string, + out io.Writer, certFile, keyFile, caFile string, insecureSkipTLSVerify, plainHTTP bool, username, password string, ) (*registry.Client, error) { if certFile != "" && keyFile != "" || caFile != "" || insecureSkipTLSVerify { - registryClient, err := newRegistryClientWithTLS(certFile, keyFile, caFile, insecureSkipTLSVerify, username, password) + registryClient, err := newRegistryClientWithTLS(out, certFile, keyFile, caFile, insecureSkipTLSVerify, username, password) if err != nil { return nil, err } return registryClient, nil } - registryClient, err := newDefaultRegistryClient(plainHTTP, username, password) + registryClient, err := newDefaultRegistryClient(out, plainHTTP, username, password) if err != nil { return nil, err } return registryClient, nil } -func newDefaultRegistryClient(plainHTTP bool, username, password string) (*registry.Client, error) { +func newDefaultRegistryClient(out io.Writer, plainHTTP bool, username, password string) (*registry.Client, error) { opts := []registry.ClientOption{ registry.ClientOptDebug(settings.Debug), registry.ClientOptEnableCache(true), - registry.ClientOptWriter(os.Stderr), + registry.ClientOptWriter(out), registry.ClientOptCredentialsFile(settings.RegistryConfig), registry.ClientOptBasicAuth(username, password), } @@ -438,7 +438,7 @@ func newDefaultRegistryClient(plainHTTP bool, username, password string) (*regis } func newRegistryClientWithTLS( - certFile, keyFile, caFile string, insecureSkipTLSVerify bool, username, password string, + out io.Writer, certFile, keyFile, caFile string, insecureSkipTLSVerify bool, username, password string, ) (*registry.Client, error) { tlsConf, err := tlsutil.NewTLSConfig( tlsutil.WithInsecureSkipVerify(insecureSkipTLSVerify), @@ -454,7 +454,7 @@ func newRegistryClientWithTLS( registryClient, err := registry.NewClient( registry.ClientOptDebug(settings.Debug), registry.ClientOptEnableCache(true), - registry.ClientOptWriter(os.Stderr), + registry.ClientOptWriter(out), registry.ClientOptCredentialsFile(settings.RegistryConfig), registry.ClientOptHTTPClient(&http.Client{ Transport: &http.Transport{ diff --git a/pkg/cmd/show.go b/pkg/cmd/show.go index d7249c3fe..a92ca75b4 100644 --- a/pkg/cmd/show.go +++ b/pkg/cmd/show.go @@ -84,7 +84,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { ValidArgsFunction: validArgsFunc, RunE: func(_ *cobra.Command, args []string) error { client.OutputFormat = action.ShowAll - err := addRegistryClient(client) + err := addRegistryClient(out, client) if err != nil { return err } @@ -105,7 +105,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { ValidArgsFunction: validArgsFunc, RunE: func(_ *cobra.Command, args []string) error { client.OutputFormat = action.ShowValues - err := addRegistryClient(client) + err := addRegistryClient(out, client) if err != nil { return err } @@ -126,7 +126,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { ValidArgsFunction: validArgsFunc, RunE: func(_ *cobra.Command, args []string) error { client.OutputFormat = action.ShowChart - err := addRegistryClient(client) + err := addRegistryClient(out, client) if err != nil { return err } @@ -147,7 +147,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { ValidArgsFunction: validArgsFunc, RunE: func(_ *cobra.Command, args []string) error { client.OutputFormat = action.ShowReadme - err := addRegistryClient(client) + err := addRegistryClient(out, client) if err != nil { return err } @@ -168,7 +168,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { ValidArgsFunction: validArgsFunc, RunE: func(_ *cobra.Command, args []string) error { client.OutputFormat = action.ShowCRDs - err := addRegistryClient(client) + err := addRegistryClient(out, client) if err != nil { return err } @@ -225,8 +225,8 @@ func runShow(args []string, client *action.Show) (string, error) { return client.Run(cp) } -func addRegistryClient(client *action.Show) error { - registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, +func addRegistryClient(out io.Writer, client *action.Show) error { + registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index e5789db8b..0e2824ce9 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -85,7 +85,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.KubeVersion = parsedKubeVersion } - registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index 43e19ab22..9ec4f4ef2 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -105,7 +105,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { client.Namespace = settings.Namespace() - registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) From a5552edf9fb0e23b475310d943a2ecd1df5aeafd Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Thu, 11 Jun 2026 15:38:02 -0600 Subject: [PATCH 350/571] fix: protect FailingKubeClient.RecordedWaitOptions from data race (#31925) * fix: protect FailingKubeClient.RecordedWaitOptions from concurrent access Add a sync.Mutex to guard the append to RecordedWaitOptions in GetWaiterWithOptions, fixing a data race detected by -race when concurrent goroutines (e.g. upgrade + rollback) both call GetWaiterWithOptions on the same FailingKubeClient instance. Fixes race failures in TestUpgradeRelease_Interrupted_RollbackOnFailure and TestInstallRelease_RollbackOnFailure_Interrupted. Signed-off-by: Terry Howe * fix: extract appendRecordedWaitOptionsLocked helper with defer unlock Signed-off-by: Terry Howe --------- Signed-off-by: Terry Howe Signed-off-by: Terry Howe --- pkg/kube/fake/failing_kube_client.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/kube/fake/failing_kube_client.go b/pkg/kube/fake/failing_kube_client.go index 0f7787f79..75d0c8de1 100644 --- a/pkg/kube/fake/failing_kube_client.go +++ b/pkg/kube/fake/failing_kube_client.go @@ -19,6 +19,7 @@ package fake import ( "io" + "sync" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -49,6 +50,7 @@ type FailingKubeClient struct { WaitDuration time.Duration // RecordedWaitOptions stores the WaitOptions passed to GetWaiter for testing RecordedWaitOptions []kube.WaitOption + mu sync.Mutex } var _ kube.Interface = &FailingKubeClient{} @@ -158,9 +160,14 @@ func (f *FailingKubeClient) GetWaiter(ws kube.WaitStrategy) (kube.Waiter, error) return f.GetWaiterWithOptions(ws) } -func (f *FailingKubeClient) GetWaiterWithOptions(ws kube.WaitStrategy, opts ...kube.WaitOption) (kube.Waiter, error) { - // Record the WaitOptions for testing +func (f *FailingKubeClient) appendRecordedWaitOptionsLocked(opts ...kube.WaitOption) { + f.mu.Lock() + defer f.mu.Unlock() f.RecordedWaitOptions = append(f.RecordedWaitOptions, opts...) +} + +func (f *FailingKubeClient) GetWaiterWithOptions(ws kube.WaitStrategy, opts ...kube.WaitOption) (kube.Waiter, error) { + f.appendRecordedWaitOptionsLocked(opts...) waiter, _ := f.PrintingKubeClient.GetWaiterWithOptions(ws, opts...) printingKubeWaiter, _ := waiter.(*PrintingKubeWaiter) return &FailingKubeWaiter{ From 1f7869c31f8d96638d238957b20a27d665001d88 Mon Sep 17 00:00:00 2001 From: Mohammad Abdolirad Date: Fri, 5 Jun 2026 00:20:46 +0200 Subject: [PATCH 351/571] fix panic on repeated IsReachable calls - only cache kube client when creation succeeds - add regression test for two failed reachability checks Signed-off-by: Mohammad Abdolirad --- pkg/kube/client.go | 13 ++++++++----- pkg/kube/client_test.go | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 04d634740..a640bde8a 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -235,12 +235,15 @@ func New(getter genericclioptions.RESTClientGetter) *Client { // getKubeClient get or create a new KubernetesClientSet func (c *Client) getKubeClient() (kubernetes.Interface, error) { - var err error - if c.kubeClient == nil { - c.kubeClient, err = c.Factory.KubernetesClientSet() + if c.kubeClient != nil { + return c.kubeClient, nil } - - return c.kubeClient, err + kc, err := c.Factory.KubernetesClientSet() + if err != nil { + return nil, err + } + c.kubeClient = kc + return c.kubeClient, nil } // IsReachable tests connectivity to the cluster. diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index cd00baa94..64fd8282d 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -1416,6 +1416,26 @@ func TestIsReachable(t *testing.T) { } } +func TestIsReachableTwiceAfterClientCreationFailure(t *testing.T) { + refusedErr := errors.New("connection refused") + client := newTestClient(t) + client.Factory = &errorFactory{err: refusedErr} + + assertReachableErr := func(label string, err error) { + t.Helper() + if err == nil { + t.Fatalf("%s: expected error, got nil", label) + } + if !errors.Is(err, refusedErr) { + t.Fatalf("%s: expected error wrapping %v, got %v", label, refusedErr, err) + } + } + + assertReachableErr("first call", client.IsReachable()) + // Second call must return the same underlying error, not panic. + assertReachableErr("second call", client.IsReachable()) +} + func TestIsIncompatibleServerError(t *testing.T) { testCases := map[string]struct { Err error From 2014946e550ed7a667b9ae8cfe15a377a2fe94ca Mon Sep 17 00:00:00 2001 From: Jeaeun Kim Date: Tue, 26 Aug 2025 13:07:00 +0900 Subject: [PATCH 352/571] chore: Improve error reporting for `helm template --debug` with `--show-only` Signed-off-by: Jeaeun Kim --- pkg/cmd/template.go | 7 ++++++- pkg/cmd/template_test.go | 6 ++++++ ...e-with-invalid-template-expr-debug-show-only.txt | 3 +++ .../Chart.yaml | 8 ++++++++ .../README.md | 13 +++++++++++++ .../templates/alpine-pod.yaml | 9 +++++++++ .../values.yaml | 1 + 7 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 pkg/cmd/testdata/output/template-with-invalid-template-expr-debug-show-only.txt create mode 100644 pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/Chart.yaml create mode 100644 pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/README.md create mode 100644 pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml create mode 100644 pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/values.yaml diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index 0e2824ce9..13eb1da04 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -186,7 +186,12 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { missing = false } if missing { - return fmt.Errorf("could not find template %s in chart", f) + if err != nil && settings.Debug { + // assume the manifest itself is too malformed to be rendered + return err + } else { + return fmt.Errorf("could not find template %s in chart", f) + } } } for _, m := range manifestsToRender { diff --git a/pkg/cmd/template_test.go b/pkg/cmd/template_test.go index 7391781f6..b43151413 100644 --- a/pkg/cmd/template_test.go +++ b/pkg/cmd/template_test.go @@ -133,6 +133,12 @@ func TestTemplateCmd(t *testing.T) { wantError: true, golden: "output/template-with-invalid-yaml-debug.txt", }, + { + name: "chart with template with invalid template expression (--debug, --show-only)", + cmd: fmt.Sprintf("template '%s' --debug --show-only %s", "testdata/testcharts/chart-with-template-with-invalid-template-expr", "templates/alpine-pod.yaml"), + wantError: true, + golden: "output/template-with-invalid-template-expr-debug-show-only.txt", + }, { name: "template skip-tests", cmd: fmt.Sprintf(`template '%s' --skip-tests`, chartPath), diff --git a/pkg/cmd/testdata/output/template-with-invalid-template-expr-debug-show-only.txt b/pkg/cmd/testdata/output/template-with-invalid-template-expr-debug-show-only.txt new file mode 100644 index 000000000..89d518bbb --- /dev/null +++ b/pkg/cmd/testdata/output/template-with-invalid-template-expr-debug-show-only.txt @@ -0,0 +1,3 @@ +Error: chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml:7:38 + executing "chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml" at : + invalid value; expected string diff --git a/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/Chart.yaml new file mode 100644 index 000000000..83541d4dd --- /dev/null +++ b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/Chart.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +description: Deploy a basic Alpine Linux pod +home: https://helm.sh/helm +name: chart-with-template-with-invalid-template-expr +sources: + - https://github.com/helm/helm +version: 0.1.0 +type: application diff --git a/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/README.md b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/README.md new file mode 100644 index 000000000..fcf7ee017 --- /dev/null +++ b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/README.md @@ -0,0 +1,13 @@ +#Alpine: A simple Helm chart + +Run a single pod of Alpine Linux. + +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.yaml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml new file mode 100644 index 000000000..bdc0fe39b --- /dev/null +++ b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{.Release.Name}}-{{.Values.Name}}" +spec: + containers: + - name: {{ .Values.nonExistentKey | b64enc }} + image: "alpine:3.9" + command: ["/bin/sleep","9000"] diff --git a/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/values.yaml new file mode 100644 index 000000000..807e12aea --- /dev/null +++ b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/values.yaml @@ -0,0 +1 @@ +Name: my-alpine From ea847fef95702ca53ec5ab3c627c696ea1488598 Mon Sep 17 00:00:00 2001 From: Jeaeun Kim Date: Wed, 27 Aug 2025 20:46:49 +0900 Subject: [PATCH 353/571] chore: store err separately for clarity Signed-off-by: Jeaeun Kim --- pkg/cmd/template.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index 13eb1da04..556822f0d 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -113,6 +113,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } return err } + savedErr := err // We ignore a potential error here because, when the --debug flag was specified, // we always want to print the YAML, even if it is not valid. The error is still returned afterwards. @@ -186,9 +187,9 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { missing = false } if missing { - if err != nil && settings.Debug { + if savedErr != nil && settings.Debug { // assume the manifest itself is too malformed to be rendered - return err + return savedErr } else { return fmt.Errorf("could not find template %s in chart", f) } @@ -202,7 +203,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } } - return err + return savedErr }, } From 99baa2edd493af15ffd98f39808fac5c4a9b12f7 Mon Sep 17 00:00:00 2001 From: Jeaeun Kim Date: Sun, 8 Mar 2026 22:05:31 +0900 Subject: [PATCH 354/571] chore: fix lint Signed-off-by: Jeaeun Kim --- pkg/cmd/template.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index 556822f0d..e56fefc17 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -190,9 +190,8 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if savedErr != nil && settings.Debug { // assume the manifest itself is too malformed to be rendered return savedErr - } else { - return fmt.Errorf("could not find template %s in chart", f) } + return fmt.Errorf("could not find template %s in chart", f) } } for _, m := range manifestsToRender { From 9c1d5a3b249c6684bfe902455a61e728bbf7dcf6 Mon Sep 17 00:00:00 2001 From: Jeaeun Kim Date: Mon, 18 May 2026 00:22:23 +0900 Subject: [PATCH 355/571] chore: rename savedErr to clear its specific purpose Signed-off-by: Jeaeun Kim --- pkg/cmd/template.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index e56fefc17..bb364231b 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -113,7 +113,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } return err } - savedErr := err + installErr := err // We ignore a potential error here because, when the --debug flag was specified, // we always want to print the YAML, even if it is not valid. The error is still returned afterwards. @@ -187,9 +187,9 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { missing = false } if missing { - if savedErr != nil && settings.Debug { + if installErr != nil && settings.Debug { // assume the manifest itself is too malformed to be rendered - return savedErr + return installErr } return fmt.Errorf("could not find template %s in chart", f) } @@ -202,7 +202,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } } - return savedErr + return installErr }, } From ed76a36ab9b4c8f5a56049aa201dd8ce04836ecb Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Tue, 16 Jun 2026 15:37:08 +1200 Subject: [PATCH 356/571] refactor(cmd/helm): convert tests to testify assert/require Replace native Go testing patterns (t.Errorf, t.Fatalf, t.Error, t.Fatal) with github.com/stretchr/testify equivalents (assert.X, require.X) for improved test readability and error messages. Signed-off-by: George Jenkins --- cmd/helm/helm_test.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index a9362f772..05d05dece 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -18,13 +18,13 @@ package main import ( "bytes" - "errors" "os" "os/exec" "runtime" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestCliPluginExitCode(t *testing.T) { @@ -62,20 +62,13 @@ func TestCliPluginExitCode(t *testing.T) { err := cmd.Run() exiterr := &exec.ExitError{} - ok := errors.As(err, &exiterr) - if !ok { - t.Fatalf("Unexpected error type returned by os.Exit: %T", err) - } + require.ErrorAs(t, err, &exiterr) assert.Empty(t, stdout.String()) expectedStderr := "level=WARN msg=\"failed to load plugin (ignoring)\" plugin_yaml=../../pkg/cmd/testdata/helmhome/helm/plugins/noversion/plugin.yaml error=\"failed to load plugin \\\"../../pkg/cmd/testdata/helmhome/helm/plugins/noversion\\\": plugin `version` is required\"\nError: plugin \"exitwith\" exited with error\n" - if stderr.String() != expectedStderr { - t.Errorf("Expected %q written to stderr: Got %q", expectedStderr, stderr.String()) - } + assert.Equal(t, expectedStderr, stderr.String()) - if exiterr.ExitCode() != 43 { - t.Errorf("Expected exit code 43: Got %d", exiterr.ExitCode()) - } + assert.Equal(t, 43, exiterr.ExitCode()) } } From f4d713abb210230b3935c4a9685f1f34a8330970 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Tue, 16 Jun 2026 16:17:14 +1200 Subject: [PATCH 357/571] refactor(pkg): convert tests to testify assert/require Convert pkg/gates, pkg/kubeenv, pkg/ignore, and pkg/helmpath test files from native Go testing patterns (t.Errorf, t.Fatalf, t.Error, t.Fatal) to github.com/stretchr/testify equivalents (assert.X, require.X). Signed-off-by: George Jenkins --- pkg/gates/gates_test.go | 18 ++++------ pkg/helmpath/home_unix_test.go | 20 ++++------- pkg/helmpath/home_windows_test.go | 15 ++++---- pkg/helmpath/lazypath_darwin_test.go | 25 ++++--------- pkg/helmpath/lazypath_unix_test.go | 25 ++++--------- pkg/helmpath/lazypath_windows_test.go | 25 ++++--------- pkg/ignore/rules_test.go | 52 +++++++++------------------ 7 files changed, 56 insertions(+), 124 deletions(-) diff --git a/pkg/gates/gates_test.go b/pkg/gates/gates_test.go index 4d77199e6..217ee1bbe 100644 --- a/pkg/gates/gates_test.go +++ b/pkg/gates/gates_test.go @@ -18,6 +18,8 @@ package gates import ( "os" "testing" + + "github.com/stretchr/testify/assert" ) const name string = "HELM_EXPERIMENTAL_FEATURE" @@ -25,31 +27,23 @@ const name string = "HELM_EXPERIMENTAL_FEATURE" func TestIsEnabled(t *testing.T) { g := Gate(name) - if g.IsEnabled() { - t.Errorf("feature gate shows as available, but the environment variable %s was not set", name) - } + assert.False(t, g.IsEnabled(), "feature gate shows as available, but the environment variable %s was not set", name) t.Setenv(name, "1") - if !g.IsEnabled() { - t.Errorf("feature gate shows as disabled, but the environment variable %s was set", name) - } + assert.True(t, g.IsEnabled(), "feature gate shows as disabled, but the environment variable %s was set", name) } func TestError(t *testing.T) { os.Unsetenv(name) g := Gate(name) - if g.Error().Error() != "this feature has been marked as experimental and is not enabled by default. Please set HELM_EXPERIMENTAL_FEATURE=1 in your environment to use this feature" { - t.Errorf("incorrect error message. Received %s", g.Error().Error()) - } + assert.Equal(t, "this feature has been marked as experimental and is not enabled by default. Please set HELM_EXPERIMENTAL_FEATURE=1 in your environment to use this feature", g.Error().Error()) } func TestString(t *testing.T) { os.Unsetenv(name) g := Gate(name) - if g.String() != "HELM_EXPERIMENTAL_FEATURE" { - t.Errorf("incorrect string representation. Received %s", g.String()) - } + assert.Equal(t, "HELM_EXPERIMENTAL_FEATURE", g.String()) } diff --git a/pkg/helmpath/home_unix_test.go b/pkg/helmpath/home_unix_test.go index a64c9bcd6..84c912502 100644 --- a/pkg/helmpath/home_unix_test.go +++ b/pkg/helmpath/home_unix_test.go @@ -16,9 +16,10 @@ package helmpath import ( - "runtime" "testing" + "github.com/stretchr/testify/assert" + "helm.sh/helm/v4/pkg/helmpath/xdg" ) @@ -26,20 +27,13 @@ func TestHelmHome(t *testing.T) { t.Setenv(xdg.CacheHomeEnvVar, "/cache") t.Setenv(xdg.ConfigHomeEnvVar, "/config") t.Setenv(xdg.DataHomeEnvVar, "/data") - isEq := func(t *testing.T, got, expected string) { - t.Helper() - if expected != got { - t.Error(runtime.GOOS) - t.Errorf("Expected %q, got %q", expected, got) - } - } - - isEq(t, CachePath(), "/cache/helm") - isEq(t, ConfigPath(), "/config/helm") - isEq(t, DataPath(), "/data/helm") + + assert.Equal(t, "/cache/helm", CachePath()) + assert.Equal(t, "/config/helm", ConfigPath()) + assert.Equal(t, "/data/helm", DataPath()) // test to see if lazy-loading environment variables at runtime works t.Setenv(xdg.CacheHomeEnvVar, "/cache2") - isEq(t, CachePath(), "/cache2/helm") + assert.Equal(t, "/cache2/helm", CachePath()) } diff --git a/pkg/helmpath/home_windows_test.go b/pkg/helmpath/home_windows_test.go index 38fe5e4f1..4c2c7d11a 100644 --- a/pkg/helmpath/home_windows_test.go +++ b/pkg/helmpath/home_windows_test.go @@ -19,6 +19,8 @@ import ( "os" "testing" + "github.com/stretchr/testify/assert" + "helm.sh/helm/v4/pkg/helmpath/xdg" ) @@ -26,18 +28,13 @@ func TestHelmHome(t *testing.T) { os.Setenv(xdg.CacheHomeEnvVar, "c:\\") os.Setenv(xdg.ConfigHomeEnvVar, "d:\\") os.Setenv(xdg.DataHomeEnvVar, "e:\\") - isEq := func(t *testing.T, a, b string) { - if a != b { - t.Errorf("Expected %q, got %q", b, a) - } - } - isEq(t, CachePath(), "c:\\helm") - isEq(t, ConfigPath(), "d:\\helm") - isEq(t, DataPath(), "e:\\helm") + assert.Equal(t, "c:\\helm", CachePath()) + assert.Equal(t, "d:\\helm", ConfigPath()) + assert.Equal(t, "e:\\helm", DataPath()) // test to see if lazy-loading environment variables at runtime works os.Setenv(xdg.CacheHomeEnvVar, "f:\\") - isEq(t, CachePath(), "f:\\helm") + assert.Equal(t, "f:\\helm", CachePath()) } diff --git a/pkg/helmpath/lazypath_darwin_test.go b/pkg/helmpath/lazypath_darwin_test.go index e3006d0d5..143750f04 100644 --- a/pkg/helmpath/lazypath_darwin_test.go +++ b/pkg/helmpath/lazypath_darwin_test.go @@ -20,6 +20,7 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" "k8s.io/client-go/util/homedir" "helm.sh/helm/v4/pkg/helmpath/xdg" @@ -36,17 +37,13 @@ func TestDataPath(t *testing.T) { expected := filepath.Join(homedir.HomeDir(), "Library", appName, testFile) - if lazy.dataPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile)) - } + assert.Equal(t, expected, lazy.dataPath(testFile)) t.Setenv(xdg.DataHomeEnvVar, "/tmp") expected = filepath.Join("/tmp", appName, testFile) - if lazy.dataPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile)) - } + assert.Equal(t, expected, lazy.dataPath(testFile)) } func TestConfigPath(t *testing.T) { @@ -54,17 +51,13 @@ func TestConfigPath(t *testing.T) { expected := filepath.Join(homedir.HomeDir(), "Library", "Preferences", appName, testFile) - if lazy.configPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile)) - } + assert.Equal(t, expected, lazy.configPath(testFile)) t.Setenv(xdg.ConfigHomeEnvVar, "/tmp") expected = filepath.Join("/tmp", appName, testFile) - if lazy.configPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile)) - } + assert.Equal(t, expected, lazy.configPath(testFile)) } func TestCachePath(t *testing.T) { @@ -72,15 +65,11 @@ func TestCachePath(t *testing.T) { expected := filepath.Join(homedir.HomeDir(), "Library", "Caches", appName, testFile) - if lazy.cachePath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile)) - } + assert.Equal(t, expected, lazy.cachePath(testFile)) t.Setenv(xdg.CacheHomeEnvVar, "/tmp") expected = filepath.Join("/tmp", appName, testFile) - if lazy.cachePath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile)) - } + assert.Equal(t, expected, lazy.cachePath(testFile)) } diff --git a/pkg/helmpath/lazypath_unix_test.go b/pkg/helmpath/lazypath_unix_test.go index 4b0f2429b..6f8c6c2e8 100644 --- a/pkg/helmpath/lazypath_unix_test.go +++ b/pkg/helmpath/lazypath_unix_test.go @@ -19,6 +19,7 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" "k8s.io/client-go/util/homedir" "helm.sh/helm/v4/pkg/helmpath/xdg" @@ -33,47 +34,35 @@ const ( func TestDataPath(t *testing.T) { expected := filepath.Join(homedir.HomeDir(), ".local", "share", appName, testFile) - if lazy.dataPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile)) - } + assert.Equal(t, expected, lazy.dataPath(testFile)) t.Setenv(xdg.DataHomeEnvVar, "/tmp") expected = filepath.Join("/tmp", appName, testFile) - if lazy.dataPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile)) - } + assert.Equal(t, expected, lazy.dataPath(testFile)) } func TestConfigPath(t *testing.T) { expected := filepath.Join(homedir.HomeDir(), ".config", appName, testFile) - if lazy.configPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile)) - } + assert.Equal(t, expected, lazy.configPath(testFile)) t.Setenv(xdg.ConfigHomeEnvVar, "/tmp") expected = filepath.Join("/tmp", appName, testFile) - if lazy.configPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile)) - } + assert.Equal(t, expected, lazy.configPath(testFile)) } func TestCachePath(t *testing.T) { expected := filepath.Join(homedir.HomeDir(), ".cache", appName, testFile) - if lazy.cachePath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile)) - } + assert.Equal(t, expected, lazy.cachePath(testFile)) t.Setenv(xdg.CacheHomeEnvVar, "/tmp") expected = filepath.Join("/tmp", appName, testFile) - if lazy.cachePath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile)) - } + assert.Equal(t, expected, lazy.cachePath(testFile)) } diff --git a/pkg/helmpath/lazypath_windows_test.go b/pkg/helmpath/lazypath_windows_test.go index ebd95e812..df8ad2d80 100644 --- a/pkg/helmpath/lazypath_windows_test.go +++ b/pkg/helmpath/lazypath_windows_test.go @@ -20,6 +20,7 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" "k8s.io/client-go/util/homedir" "helm.sh/helm/v4/pkg/helmpath/xdg" @@ -37,17 +38,13 @@ func TestDataPath(t *testing.T) { expected := filepath.Join(homedir.HomeDir(), "foo", appName, testFile) - if lazy.dataPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile)) - } + assert.Equal(t, expected, lazy.dataPath(testFile)) os.Setenv(xdg.DataHomeEnvVar, filepath.Join(homedir.HomeDir(), "xdg")) expected = filepath.Join(homedir.HomeDir(), "xdg", appName, testFile) - if lazy.dataPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.dataPath(testFile)) - } + assert.Equal(t, expected, lazy.dataPath(testFile)) } func TestConfigPath(t *testing.T) { @@ -56,17 +53,13 @@ func TestConfigPath(t *testing.T) { expected := filepath.Join(homedir.HomeDir(), "foo", appName, testFile) - if lazy.configPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile)) - } + assert.Equal(t, expected, lazy.configPath(testFile)) os.Setenv(xdg.ConfigHomeEnvVar, filepath.Join(homedir.HomeDir(), "xdg")) expected = filepath.Join(homedir.HomeDir(), "xdg", appName, testFile) - if lazy.configPath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.configPath(testFile)) - } + assert.Equal(t, expected, lazy.configPath(testFile)) } func TestCachePath(t *testing.T) { @@ -75,15 +68,11 @@ func TestCachePath(t *testing.T) { expected := filepath.Join(homedir.HomeDir(), "foo", appName, testFile) - if lazy.cachePath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile)) - } + assert.Equal(t, expected, lazy.cachePath(testFile)) os.Setenv(xdg.CacheHomeEnvVar, filepath.Join(homedir.HomeDir(), "xdg")) expected = filepath.Join(homedir.HomeDir(), "xdg", appName, testFile) - if lazy.cachePath(testFile) != expected { - t.Errorf("expected '%s', got '%s'", expected, lazy.cachePath(testFile)) - } + assert.Equal(t, expected, lazy.cachePath(testFile)) } diff --git a/pkg/ignore/rules_test.go b/pkg/ignore/rules_test.go index 9581cf09f..02ba63588 100644 --- a/pkg/ignore/rules_test.go +++ b/pkg/ignore/rules_test.go @@ -21,6 +21,9 @@ import ( "os" "path/filepath" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var testdata = "./testdata" @@ -36,22 +39,14 @@ baz/bar/foo.txt one/more ` r, err := parseString(rules) - if err != nil { - t.Fatalf("Error parsing rules: %s", err) - } + require.NoError(t, err, "Error parsing rules") - if len(r.patterns) != 4 { - t.Errorf("Expected 4 rules, got %d", len(r.patterns)) - } + assert.Len(t, r.patterns, 4) expects := []string{"foo", "bar/*", "baz/bar/foo.txt", "one/more"} for i, p := range r.patterns { - if p.raw != expects[i] { - t.Errorf("Expected %q, got %q", expects[i], p.raw) - } - if p.match == nil { - t.Errorf("Expected %s to have a matcher function.", p.raw) - } + assert.Equal(t, expects[i], p.raw) + assert.NotNil(t, p.match, "Expected %s to have a matcher function.", p.raw) } } @@ -59,26 +54,19 @@ func TestParseFail(t *testing.T) { shouldFail := []string{"foo/**/bar", "[z-"} for _, fail := range shouldFail { _, err := parseString(fail) - if err == nil { - t.Errorf("Rule %q should have failed", fail) - } + assert.Error(t, err, "Rule %q should have failed", fail) } } func TestParseFile(t *testing.T) { f := filepath.Join(testdata, HelmIgnore) - if _, err := os.Stat(f); err != nil { - t.Fatalf("Fixture %s missing: %s", f, err) - } + _, err := os.Stat(f) + require.NoError(t, err, "Fixture %s missing", f) r, err := ParseFile(f) - if err != nil { - t.Fatalf("Failed to parse rules file: %s", err) - } + require.NoError(t, err, "Failed to parse rules file") - if len(r.patterns) != 3 { - t.Errorf("Expected 3 patterns, got %d", len(r.patterns)) - } + assert.Len(t, r.patterns, 3) } func TestIgnore(t *testing.T) { @@ -126,17 +114,11 @@ func TestIgnore(t *testing.T) { for _, test := range tests { r, err := parseString(test.pattern) - if err != nil { - t.Fatalf("Failed to parse: %s", err) - } + require.NoError(t, err, "Failed to parse: %s", test.pattern) fi, err := os.Stat(filepath.Join(testdata, test.name)) - if err != nil { - t.Fatalf("Fixture missing: %s", err) - } + require.NoError(t, err, "Fixture missing: %s", test.name) - if r.Ignore(test.name, fi) != test.expect { - t.Errorf("Expected %q to be %v for pattern %q", test.name, test.expect, test.pattern) - } + assert.Equal(t, test.expect, r.Ignore(test.name, fi), "Expected %q to be %v for pattern %q", test.name, test.expect, test.pattern) } } @@ -144,9 +126,7 @@ func TestAddDefaults(t *testing.T) { r := Rules{} r.AddDefaults() - if len(r.patterns) != 1 { - t.Errorf("Expected 1 default patterns, got %d", len(r.patterns)) - } + assert.Len(t, r.patterns, 1) } func parseString(str string) (*Rules, error) { From fa9cb4919423e80d1693275396f9d8fa6b324baf Mon Sep 17 00:00:00 2001 From: devShaik010 <20dpcs044hy@manuu.edu.in> Date: Tue, 16 Jun 2026 20:00:17 +0530 Subject: [PATCH 358/571] docs: improve CONTRIBUTING.md wording Signed-off-by: devShaik010 <20dpcs044hy@manuu.edu.in> --- CONTRIBUTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7aa19972f..0d7bc4f4b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ # Contributing Guidelines The Helm project accepts contributions via GitHub pull requests. This document outlines the process -to help get your contribution accepted. +to help you get your contribution accepted. ## Reporting a Security Issue @@ -110,9 +110,9 @@ Whether you are a user or contributor, official support channels include: - User: [#helm-users](https://kubernetes.slack.com/messages/C0NH30761/details/) - Contributor: [#helm-dev](https://kubernetes.slack.com/messages/C51E88VDG/) -Before opening a new issue or submitting a new pull request, it's helpful to search the project - -it's likely that another user has already reported the issue you're facing, or it's a known issue -that we're already aware of. It is also worth asking on the Slack channels. +Before opening a new issue or submitting a new pull request, it helps to search the project first. +Another user may have already reported the issue you are facing, or it may already be a known issue. +It is also worth asking on the Slack channels. ## Milestones @@ -137,7 +137,7 @@ are removed or substantially modified (unless we need to fix a security issue). We also remain committed to not changing publicly accessible Go library definitions inside of the `pkg/` directory of our source code in a non-backwards-compatible way. -For more details on Helm’s minor and patch release backwards-compatibility rules, please read [HIP-0004](https://github.com/helm/community/blob/main/hips/hip-0004.md) +For more details on Helm’s minor and patch release backwards-compatibility rules, please read [HIP-0004](https://github.com/helm/community/blob/main/hips/hip-0004.md). For a quick summary of our backward compatibility guidelines for releases between 3.0 and 4.0: From 9ad5620edc1b51507d111f7f998cc0f5b95e5726 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Tue, 16 Jun 2026 12:16:54 -0700 Subject: [PATCH 359/571] Update pkg/ignore/rules_test.go Co-authored-by: Terry Howe Signed-off-by: George Jenkins --- pkg/ignore/rules_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/ignore/rules_test.go b/pkg/ignore/rules_test.go index 02ba63588..30e157a52 100644 --- a/pkg/ignore/rules_test.go +++ b/pkg/ignore/rules_test.go @@ -41,7 +41,7 @@ one/more r, err := parseString(rules) require.NoError(t, err, "Error parsing rules") - assert.Len(t, r.patterns, 4) + require.Len(t, r.patterns, 4) expects := []string{"foo", "bar/*", "baz/bar/foo.txt", "one/more"} for i, p := range r.patterns { From d3bd09147bbae09ca85615452bda31b8215e66f5 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Wed, 17 Jun 2026 13:21:05 +1200 Subject: [PATCH 360/571] Revert "fix(kube): prevent spurious early exit in WaitForDelete during informer sync" Signed-off-by: George Jenkins --- pkg/kube/statuswait.go | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index adc7a425a..29de0af2b 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -167,15 +167,7 @@ func (w *statusWaiter) waitForDelete(ctx context.Context, resourceList ResourceL rs.Identifier.GroupKind.Kind, rs.Identifier.Namespace, rs.Identifier.Name, rs.Status, rs.Message)) } if err := ctx.Err(); err != nil { - // context.Canceled and other non-deadline errors always propagate: they signal an - // external interruption regardless of resource state. - // context.DeadlineExceeded is only added when there are resource-specific errors; - // if all resources are Unknown or NotFound the timeout is not itself a failure for - // a delete wait (e.g. resources deleted before the watch started stay Unknown in - // the fake client but are effectively gone). - if !errors.Is(err, context.DeadlineExceeded) || len(errs) > 0 { - errs = append(errs, err) - } + errs = append(errs, err) } if len(errs) > 0 { return errors.Join(errs...) @@ -248,7 +240,6 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl return func(statusCollector *collector.ResourceStatusCollector, _ event.Event) { var rss []*event.ResourceStatus var nonDesiredResources []*event.ResourceStatus - var unknownSkipped int for _, rs := range statusCollector.ResourceStatuses { if rs == nil { continue @@ -256,7 +247,6 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl // If a resource is already deleted before waiting has started, it will show as unknown. // This check ensures we don't wait forever for a resource that is already deleted. if rs.Status == status.UnknownStatus && desired == status.NotFoundStatus { - unknownSkipped++ continue } // Failed is a terminal state. This check ensures we don't wait forever for a resource @@ -270,14 +260,6 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl } } - // During informer initialization there is a brief window where existing resources - // appear as Unknown before their real status is delivered. If every resource was - // skipped as Unknown, we cannot yet distinguish "all deleted" from "not yet synced", - // so hold off on the early-cancel to avoid a spurious success or premature exit. - if unknownSkipped > 0 && len(rss) == 0 { - return - } - if aggregator.AggregateStatus(rss, desired) == desired { logger.Debug("all resources achieved desired status", "desiredStatus", desired, "resourceCount", len(rss)) cancel() From 16b0760dfd99b5c501cbfc2138a207e1b3512c0f Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Wed, 17 Jun 2026 21:28:58 +1200 Subject: [PATCH 361/571] refactor(pkg/cli): convert tests to testify assert/require Replace native Go testing patterns (t.Errorf, t.Fatalf, t.Error, t.Fatal) with github.com/stretchr/testify equivalents (assert.X, require.X) for improved test readability and error messages. Signed-off-by: George Jenkins --- pkg/cli/environment_test.go | 62 ++++++++++------------------------ pkg/cli/values/options_test.go | 61 +++++++++++---------------------- 2 files changed, 37 insertions(+), 86 deletions(-) diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go index 52326eeff..f1067f6c6 100644 --- a/pkg/cli/environment_test.go +++ b/pkg/cli/environment_test.go @@ -23,6 +23,8 @@ import ( "testing" "github.com/spf13/pflag" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/internal/version" ) @@ -30,14 +32,10 @@ import ( func TestSetNamespace(t *testing.T) { settings := New() - if settings.namespace != "" { - t.Errorf("Expected empty namespace, got %s", settings.namespace) - } + assert.Empty(t, settings.namespace) settings.SetNamespace("testns") - if settings.namespace != "testns" { - t.Errorf("Expected namespace testns, got %s", settings.namespace) - } + assert.Equal(t, "testns", settings.namespace) } func TestEnvSettings(t *testing.T) { @@ -134,36 +132,16 @@ func TestEnvSettings(t *testing.T) { settings.AddFlags(flags) flags.Parse(strings.Split(tt.args, " ")) - if settings.Debug != tt.debug { - t.Errorf("expected debug %t, got %t", tt.debug, settings.Debug) - } - if settings.Namespace() != tt.ns { - t.Errorf("expected namespace %q, got %q", tt.ns, settings.Namespace()) - } - if settings.KubeContext != tt.kcontext { - t.Errorf("expected kube-context %q, got %q", tt.kcontext, settings.KubeContext) - } - if settings.MaxHistory != tt.maxhistory { - t.Errorf("expected maxHistory %d, got %d", tt.maxhistory, settings.MaxHistory) - } - if tt.kubeAsUser != settings.KubeAsUser { - t.Errorf("expected kAsUser %q, got %q", tt.kubeAsUser, settings.KubeAsUser) - } - if !reflect.DeepEqual(tt.kubeAsGroups, settings.KubeAsGroups) { - t.Errorf("expected kAsGroups %+v, got %+v", len(tt.kubeAsGroups), len(settings.KubeAsGroups)) - } - if tt.kubeCaFile != settings.KubeCaFile { - t.Errorf("expected kCaFile %q, got %q", tt.kubeCaFile, settings.KubeCaFile) - } - if tt.burstLimit != settings.BurstLimit { - t.Errorf("expected BurstLimit %d, got %d", tt.burstLimit, settings.BurstLimit) - } - if tt.kubeInsecure != settings.KubeInsecureSkipTLSVerify { - t.Errorf("expected kubeInsecure %t, got %t", tt.kubeInsecure, settings.KubeInsecureSkipTLSVerify) - } - if tt.kubeTLSServer != settings.KubeTLSServerName { - t.Errorf("expected kubeTLSServer %q, got %q", tt.kubeTLSServer, settings.KubeTLSServerName) - } + assert.Equal(t, tt.debug, settings.Debug, "debug") + assert.Equal(t, tt.ns, settings.Namespace(), "namespace") + assert.Equal(t, tt.kcontext, settings.KubeContext, "kube-context") + assert.Equal(t, tt.maxhistory, settings.MaxHistory, "maxHistory") + assert.Equal(t, tt.kubeAsUser, settings.KubeAsUser, "kubeAsUser") + assert.True(t, reflect.DeepEqual(tt.kubeAsGroups, settings.KubeAsGroups), "kubeAsGroups") + assert.Equal(t, tt.kubeCaFile, settings.KubeCaFile, "kubeCaFile") + assert.Equal(t, tt.burstLimit, settings.BurstLimit, "burstLimit") + assert.Equal(t, tt.kubeInsecure, settings.KubeInsecureSkipTLSVerify, "kubeInsecure") + assert.Equal(t, tt.kubeTLSServer, settings.KubeTLSServerName, "kubeTLSServer") }) } } @@ -235,9 +213,7 @@ func TestEnvOrBool(t *testing.T) { t.Setenv(tt.env, tt.val) } actual := envBoolOr(tt.env, tt.def) - if actual != tt.expected { - t.Errorf("expected result %t, got %t", tt.expected, actual) - } + assert.Equal(t, tt.expected, actual) }) } } @@ -247,14 +223,10 @@ func TestUserAgentHeaderInK8sRESTClientConfig(t *testing.T) { settings := New() restConfig, err := settings.RESTClientGetter().ToRESTConfig() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) expectedUserAgent := version.GetUserAgent() - if restConfig.UserAgent != expectedUserAgent { - t.Errorf("expected User-Agent header %q in K8s REST client config, got %q", expectedUserAgent, restConfig.UserAgent) - } + assert.Equal(t, expectedUserAgent, restConfig.UserAgent) } func resetEnv() func() { diff --git a/pkg/cli/values/options_test.go b/pkg/cli/values/options_test.go index 571622d80..3a33da7f1 100644 --- a/pkg/cli/values/options_test.go +++ b/pkg/cli/values/options_test.go @@ -22,9 +22,11 @@ import ( "os" "path/filepath" "reflect" - "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/getter" ) @@ -90,10 +92,7 @@ func TestReadFile(t *testing.T) { tmpDir := t.TempDir() filePath := filepath.Join(tmpDir, "test.txt") content := []byte("local file content") - err := os.WriteFile(filePath, content, 0644) - if err != nil { - t.Fatal(err) - } + require.NoError(t, os.WriteFile(filePath, content, 0644)) return filePath, func() {} // cleanup handled by t.TempDir() }, expectError: false, @@ -154,10 +153,7 @@ func TestReadFile(t *testing.T) { fileName := "ftp_file.txt" // Valid filename for filesystem filePath := filepath.Join(tmpDir, fileName) content := []byte("local fallback content") - err := os.WriteFile(filePath, content, 0644) - if err != nil { - t.Fatal(err) - } + require.NoError(t, os.WriteFile(filePath, content, 0644)) return filePath, func() {} }, expectError: false, @@ -202,9 +198,7 @@ func TestReadFile(t *testing.T) { // Create a pipe for stdin r, w, err := os.Pipe() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer r.Close() defer w.Close() @@ -220,28 +214,21 @@ func TestReadFile(t *testing.T) { // Test the function got, err := readFile(actualFilePath, tt.providers) - if err != nil { - t.Errorf("readFile() error = %v, expected no error for stdin", err) - return - } - - if !bytes.Equal(got, testData) { - t.Errorf("readFile() = %v, want %v", got, testData) - } + assert.NoError(t, err, "readFile() expected no error for stdin") + assert.Equal(t, testData, got) return } // Regular test cases got, err := readFile(actualFilePath, tt.providers) - if (err != nil) != tt.expectError { - t.Errorf("readFile() error = %v, expectError %v", err, tt.expectError) + if tt.expectError { + assert.Error(t, err) return } + assert.NoError(t, err) - if !tt.expectError && tt.expectedData != nil { - if !bytes.Equal(got, tt.expectedData) { - t.Errorf("readFile() = %v, want %v", got, tt.expectedData) - } + if tt.expectedData != nil { + assert.Equal(t, tt.expectedData, got) } }) } @@ -272,13 +259,8 @@ func TestReadFileErrorMessages(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := readFile(tt.filePath, tt.providers) - if err == nil { - t.Errorf("readFile() expected error containing %q, got nil", tt.wantErr) - return - } - if !strings.Contains(err.Error(), tt.wantErr) { - t.Errorf("readFile() error = %v, want error containing %q", err, tt.wantErr) - } + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) }) } } @@ -288,9 +270,7 @@ func TestReadFileOriginal(t *testing.T) { var p getter.Providers filePath := "%a.txt" _, err := readFile(filePath, p) - if err == nil { - t.Error("Expected error when has special strings") - } + assert.Error(t, err, "Expected error when has special strings") } func TestMergeValuesCLI(t *testing.T) { @@ -376,13 +356,12 @@ func TestMergeValuesCLI(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.opts.MergeValues(getter.Providers{}) - if (err != nil) != tt.wantErr { - t.Errorf("MergeValues() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + assert.Error(t, err) return } - if !tt.wantErr && !reflect.DeepEqual(got, tt.expected) { - t.Errorf("MergeValues() = %v, want %v", got, tt.expected) - } + assert.NoError(t, err) + assert.True(t, reflect.DeepEqual(got, tt.expected), "MergeValues() = %v, want %v", got, tt.expected) }) } } From daa045d4923007c1ab815b39038b81491506dc0a Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Wed, 17 Jun 2026 21:52:08 +1200 Subject: [PATCH 362/571] refactor(pkg/strvals): convert tests to testify assert/require Replace native Go testing patterns (t.Errorf, t.Fatalf, t.Error, t.Fatal) with github.com/stretchr/testify equivalents (assert.X, require.X) for improved test readability and error messages. Signed-off-by: George Jenkins --- pkg/strvals/literal_parser_test.go | 45 +++++------ pkg/strvals/parser_test.go | 119 +++++++++++++---------------- 2 files changed, 73 insertions(+), 91 deletions(-) diff --git a/pkg/strvals/literal_parser_test.go b/pkg/strvals/literal_parser_test.go index cc3d73d50..d7892844f 100644 --- a/pkg/strvals/literal_parser_test.go +++ b/pkg/strvals/literal_parser_test.go @@ -20,6 +20,9 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "sigs.k8s.io/yaml" ) @@ -278,27 +281,25 @@ func TestParseLiteral(t *testing.T) { got, err := ParseLiteral(tt.str) if err != nil { if !tt.err { - t.Fatalf("%s: %s", tt.str, err) + require.NoError(t, err, tt.str) } continue } if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.str) + assert.Fail(t, "Expected error. Got nil", tt.str) } y1, err := yaml.Marshal(tt.expect) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) y2, err := yaml.Marshal(got) if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) + require.NoError(t, err, "Error serializing parsed value") } if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.str, y1, y2) + assert.Equal(t, string(y1), string(y2), tt.str) } } } @@ -385,33 +386,31 @@ func TestParseLiteralInto(t *testing.T) { for _, tt := range tests { if err := ParseLiteralInto(tt.input, tt.got); err != nil { - t.Fatal(err) + require.NoError(t, err) } if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.input) + assert.Fail(t, "Expected error. Got nil", tt.input) } if tt.input2 != "" { if err := ParseLiteralInto(tt.input2, tt.got); err != nil { - t.Fatal(err) + require.NoError(t, err) } if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.input2) + assert.Fail(t, "Expected error. Got nil", tt.input2) } } y1, err := yaml.Marshal(tt.expect) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) y2, err := yaml.Marshal(tt.got) if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) + require.NoError(t, err, "Error serializing parsed value") } if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.input, y1, y2) + assert.Equal(t, string(y1), string(y2), tt.input) } } } @@ -452,30 +451,28 @@ func TestParseLiteralNestedLevels(t *testing.T) { if tt.err { if tt.errStr != "" { if err.Error() != tt.errStr { - t.Errorf("Expected error: %s. Got error: %s", tt.errStr, err.Error()) + assert.Equal(t, tt.errStr, err.Error()) } } continue } - t.Fatalf("%s: %s", tt.str, err) + require.NoError(t, err, tt.str) } if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.str) + assert.Fail(t, "Expected error. Got nil", tt.str) } y1, err := yaml.Marshal(tt.expect) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) y2, err := yaml.Marshal(got) if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) + require.NoError(t, err, "Error serializing parsed value") } if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.str, y1, y2) + assert.Equal(t, string(y1), string(y2), tt.str) } } } diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go index e3f8b9d8e..1a9559236 100644 --- a/pkg/strvals/parser_test.go +++ b/pkg/strvals/parser_test.go @@ -20,6 +20,9 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "sigs.k8s.io/yaml" ) @@ -78,24 +81,24 @@ func TestSetIndex(t *testing.T) { got, err := setIndex(tt.initial, tt.add, tt.val) if err != nil && tt.err == false { - t.Fatalf("%s: Expected no error but error returned", tt.name) + require.Fail(t, "Expected no error but error returned", tt.name) } else if err == nil && tt.err == true { - t.Fatalf("%s: Expected error but no error returned", tt.name) + require.Fail(t, "Expected error but no error returned", tt.name) } if len(got) != len(tt.expect) { - t.Fatalf("%s: Expected length %d, got %d", tt.name, len(tt.expect), len(got)) + require.Len(t, got, len(tt.expect), tt.name) } if !tt.err { if gg := got[tt.add].(int); gg != tt.val { - t.Errorf("%s, Expected value %d, got %d", tt.name, tt.val, gg) + assert.Equal(t, tt.val, gg, tt.name) } } for k, v := range got { if v != tt.expect[k] { - t.Errorf("%s, Expected value %d, got %d", tt.name, tt.expect[k], v) + assert.Equal(t, tt.expect[k], v, tt.name) } } } @@ -384,23 +387,21 @@ func TestParseSet(t *testing.T) { if tt.err { continue } - t.Fatalf("%s: %s", tt.str, err) + require.NoError(t, err, tt.str) } if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.str) + assert.Fail(t, "Expected error. Got nil", tt.str) } y1, err := yaml.Marshal(tt.expect) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) y2, err := yaml.Marshal(got) if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) + require.NoError(t, err, "Error serializing parsed value") } if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.str, y1, y2) + assert.Equal(t, string(y1), string(y2), tt.str) } } for _, tt := range testsString { @@ -409,23 +410,21 @@ func TestParseSet(t *testing.T) { if tt.err { continue } - t.Fatalf("%s: %s", tt.str, err) + require.NoError(t, err, tt.str) } if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.str) + assert.Fail(t, "Expected error. Got nil", tt.str) } y1, err := yaml.Marshal(tt.expect) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) y2, err := yaml.Marshal(got) if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) + require.NoError(t, err, "Error serializing parsed value") } if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.str, y1, y2) + assert.Equal(t, string(y1), string(y2), tt.str) } } } @@ -513,32 +512,30 @@ func TestParseInto(t *testing.T) { } for _, tt := range tests { if err := ParseInto(tt.input, tt.got); err != nil { - t.Fatal(err) + require.NoError(t, err) } if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.input) + assert.Fail(t, "Expected error. Got nil", tt.input) } if tt.input2 != "" { if err := ParseInto(tt.input2, tt.got); err != nil { - t.Fatal(err) + require.NoError(t, err) } if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.input2) + assert.Fail(t, "Expected error. Got nil", tt.input2) } } y1, err := yaml.Marshal(tt.expect) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) y2, err := yaml.Marshal(tt.got) if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) + require.NoError(t, err, "Error serializing parsed value") } if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.input, y1, y2) + assert.Equal(t, string(y1), string(y2), tt.input) } } } @@ -560,20 +557,18 @@ func TestParseIntoString(t *testing.T) { } if err := ParseIntoString(input, got); err != nil { - t.Fatal(err) + require.NoError(t, err) } y1, err := yaml.Marshal(expect) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) y2, err := yaml.Marshal(got) if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) + require.NoError(t, err, "Error serializing parsed value") } if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2) + assert.Equal(t, string(y1), string(y2), input) } } @@ -658,22 +653,22 @@ func TestParseJSON(t *testing.T) { if tt.err { continue } - t.Fatalf("%s: %s", tt.input, err) + require.NoError(t, err, tt.input) } if tt.err { - t.Fatalf("%s: Expected error. Got nil", tt.input) + require.Fail(t, "Expected error. Got nil", tt.input) } y1, err := yaml.Marshal(tt.expect) if err != nil { - t.Fatalf("Error serializing expected value: %s", err) + require.NoError(t, err, "Error serializing expected value") } y2, err := yaml.Marshal(tt.got) if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) + require.NoError(t, err, "Error serializing parsed value") } if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.input, y1, y2) + assert.Equal(t, string(y1), string(y2), tt.input) } } } @@ -686,28 +681,24 @@ func TestParseFile(t *testing.T) { rs2v := func(rs []rune) (any, error) { v := string(rs) if v != "path1" { - t.Errorf("%s: runesToVal: Expected value path1, got %s", input, v) + assert.Equal(t, "path1", v, "%s: runesToVal", input) return "", nil } return "value1", nil } got, err := ParseFile(input, rs2v) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) y1, err := yaml.Marshal(expect) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) y2, err := yaml.Marshal(got) if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) + require.NoError(t, err, "Error serializing parsed value") } if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2) + assert.Equal(t, string(y1), string(y2), input) } } @@ -720,27 +711,25 @@ func TestParseIntoFile(t *testing.T) { rs2v := func(rs []rune) (any, error) { v := string(rs) if v != "path1" { - t.Errorf("%s: runesToVal: Expected value path1, got %s", input, v) + assert.Equal(t, "path1", v, "%s: runesToVal", input) return "", nil } return "value1", nil } if err := ParseIntoFile(input, got, rs2v); err != nil { - t.Fatal(err) + require.NoError(t, err) } y1, err := yaml.Marshal(expect) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) y2, err := yaml.Marshal(got) if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) + require.NoError(t, err, "Error serializing parsed value") } if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2) + assert.Equal(t, string(y1), string(y2), input) } } @@ -748,12 +737,10 @@ func TestToYAML(t *testing.T) { // The TestParse does the hard part. We just verify that YAML formatting is // happening. o, err := ToYAML("name=value") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) expect := "name: value" if o != expect { - t.Errorf("Expected %q, got %q", expect, o) + assert.Equal(t, expect, o) } } @@ -792,28 +779,26 @@ func TestParseSetNestedLevels(t *testing.T) { if tt.err { if tt.errStr != "" { if err.Error() != tt.errStr { - t.Errorf("Expected error: %s. Got error: %s", tt.errStr, err.Error()) + assert.Equal(t, tt.errStr, err.Error()) } } continue } - t.Fatalf("%s: %s", tt.str, err) + require.NoError(t, err, tt.str) } if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.str) + assert.Fail(t, "Expected error. Got nil", tt.str) } y1, err := yaml.Marshal(tt.expect) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) y2, err := yaml.Marshal(got) if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) + require.NoError(t, err, "Error serializing parsed value") } if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.str, y1, y2) + assert.Equal(t, string(y1), string(y2), tt.str) } } } From 3d2ff2a1aa3d8696105af5367a254c6824dff4a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 18:51:52 +0000 Subject: [PATCH 363/571] chore(deps): bump actions/checkout from 6.0.2 to 6.0.3 Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2 to 6.0.3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 32c524ed3..f12ca5f00 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # pin@v6.0.3 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 008d6a594..38aa0a8ca 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -44,7 +44,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # pin@v6.0.3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 734bc682a..eaf36d257 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # pin@v6.0.3 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 6453ddd40..9ca2f643f 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # pin@v6.0.3 with: persist-credentials: false - name: Add variables to environment file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ab8a4a509..99c5ddcd1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest-16-cores steps: - name: Checkout source code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # pin@v6.0.3 with: fetch-depth: 0 @@ -85,7 +85,7 @@ jobs: if: github.ref == 'refs/heads/main' && github.repository == 'helm/helm' steps: - name: Checkout source code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # pin@v6.0.3 with: fetch-depth: 0 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 55a9aec82..b7a27a64a 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -28,7 +28,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false From fa68e6cb094e84076bfad53af494d7db7642ceee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 21:33:39 +0000 Subject: [PATCH 364/571] chore(deps): bump the k8s-io group across 1 directory with 7 updates Bumps the k8s-io group with 4 updates in the / directory: [k8s.io/api](https://github.com/kubernetes/api), [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver), [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime) and [k8s.io/kubectl](https://github.com/kubernetes/kubectl). Updates `k8s.io/api` from 0.36.1 to 0.36.2 - [Commits](https://github.com/kubernetes/api/compare/v0.36.1...v0.36.2) Updates `k8s.io/apiextensions-apiserver` from 0.36.1 to 0.36.2 - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.36.1...v0.36.2) Updates `k8s.io/apimachinery` from 0.36.1 to 0.36.2 - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.36.1...v0.36.2) Updates `k8s.io/apiserver` from 0.36.1 to 0.36.2 - [Commits](https://github.com/kubernetes/apiserver/compare/v0.36.1...v0.36.2) Updates `k8s.io/cli-runtime` from 0.36.1 to 0.36.2 - [Commits](https://github.com/kubernetes/cli-runtime/compare/v0.36.1...v0.36.2) Updates `k8s.io/client-go` from 0.36.1 to 0.36.2 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.36.1...v0.36.2) Updates `k8s.io/kubectl` from 0.36.1 to 0.36.2 - [Commits](https://github.com/kubernetes/kubectl/compare/v0.36.1...v0.36.2) --- updated-dependencies: - dependency-name: k8s.io/api dependency-version: 0.36.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apiextensions-apiserver dependency-version: 0.36.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apimachinery dependency-version: 0.36.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apiserver dependency-version: 0.36.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/cli-runtime dependency-version: 0.36.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/client-go dependency-version: 0.36.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/kubectl dependency-version: 0.36.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io ... Signed-off-by: dependabot[bot] --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index e04604af0..72a708cf3 100644 --- a/go.mod +++ b/go.mod @@ -39,14 +39,14 @@ require ( golang.org/x/term v0.44.0 golang.org/x/text v0.38.0 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.36.1 - k8s.io/apiextensions-apiserver v0.36.1 - k8s.io/apimachinery v0.36.1 - k8s.io/apiserver v0.36.1 - k8s.io/cli-runtime v0.36.1 - k8s.io/client-go v0.36.1 + k8s.io/api v0.36.2 + k8s.io/apiextensions-apiserver v0.36.2 + k8s.io/apimachinery v0.36.2 + k8s.io/apiserver v0.36.2 + k8s.io/cli-runtime v0.36.2 + k8s.io/client-go v0.36.2 k8s.io/klog/v2 v2.140.0 - k8s.io/kubectl v0.36.1 + k8s.io/kubectl v0.36.2 oras.land/oras-go/v2 v2.6.1 sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/kustomize/kyaml v0.21.1 @@ -171,7 +171,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/component-base v0.36.1 // indirect + k8s.io/component-base v0.36.2 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect diff --git a/go.sum b/go.sum index c0da36a37..b5a6777a6 100644 --- a/go.sum +++ b/go.sum @@ -485,26 +485,26 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY= -k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo= -k8s.io/apiextensions-apiserver v0.36.1 h1:6JfYmPUsuUIHuN+3QxutXYWj492RqF5fBSx67GYK5Ks= -k8s.io/apiextensions-apiserver v0.36.1/go.mod h1:pLzZin90riwisdzKwv/GoTwENooytoIx5zWJb4Hkby8= -k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA= -k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8= -k8s.io/apiserver v0.36.1 h1:iMS5V+rPUertv5P9RaqJgmHHTuh4quWpoxchvMUY+JY= -k8s.io/apiserver v0.36.1/go.mod h1:Cby1PbLWztu0GDOxoO6iFOyyqIsziHNEW+w9zVQ22Kw= -k8s.io/cli-runtime v0.36.1 h1:yuC/BGnnj1YYPh6D1P+pZnzinCs6DvMq86yAeNqoqzM= -k8s.io/cli-runtime v0.36.1/go.mod h1:ZQWHGt8xAF7KnviB79vX0lYNyUUqKIpU+LQg7exuFAw= -k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0= -k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU= -k8s.io/component-base v0.36.1 h1:iG6GsELftXqTNG9HG6kiVjatSgAw1sf5pJ6R5a6N0kA= -k8s.io/component-base v0.36.1/go.mod h1:nf9XPlntRdqO6WMeEWAA5F93Y4ICZQdeT9GeqLDB3JI= +k8s.io/api v0.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY= +k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg= +k8s.io/apiextensions-apiserver v0.36.2 h1:3O5gqOj/dt2XWWbpMe+TXWpE9yU6pjM/tXxtHHJT/K4= +k8s.io/apiextensions-apiserver v0.36.2/go.mod h1:cL1tBWe8XSaP1H30iWKGo7hf6iAUUUJPEU70dskmAnA= +k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ= +k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4= +k8s.io/apiserver v0.36.2 h1:6vMnkmHZPeBloNkHUhmZYq7Ylv8WIB8xjyEl+eSt26E= +k8s.io/apiserver v0.36.2/go.mod h1:9PoQ2ikCytrZyZg11mGhLEF5m8Rgsb5FJmYJ4Wvnl1k= +k8s.io/cli-runtime v0.36.2 h1:CconTvEeV4DJs4ZX3HQKCFbFRGsm6OtuBM9yjmMP2VM= +k8s.io/cli-runtime v0.36.2/go.mod h1:LddcjiMf4YlnHO7c1Y7rEtDqL84FyiYVLco7V679GUU= +k8s.io/client-go v0.36.2 h1:bfgxmFKc9CgqsgX4xKLAAdmTQlWee7Ob/HlDOrJ5TBI= +k8s.io/client-go v0.36.2/go.mod h1:1vgO4OAlfPnoLcb+Rze2GF5rAr14w8qjrYMoyXJzQj0= +k8s.io/component-base v0.36.2 h1:Z0VH80O7Ng0HDZnZj3WRR3urEGa0kTwmO8CwEwjVK1w= +k8s.io/component-base v0.36.2/go.mod h1:mGfFOA7Gwpdm1VW2cwSQYbiDIlz8GD2WGwH88QSeCyA= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/kubectl v0.36.1 h1:96HqS9twIdHM0MlJLTwbo14b9kUKPkOzZ4tlRDLv4qI= -k8s.io/kubectl v0.36.1/go.mod h1:/DGPAIewKsFWF9VFgGvkPhao2Ev4SNuE3BioZo8yPbk= +k8s.io/kubectl v0.36.2 h1:rpUGGpeL09XVOLep2yle5jrtk//JA1L6ZHfkQQtVEwk= +k8s.io/kubectl v0.36.2/go.mod h1:gVbQ3B/yb4bSR2ggQ7rd0W6icUSWs7sduH4e16Vii+0= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= From dd442c0cc7c3d7f397bbf93ac5a9551a4e2f0139 Mon Sep 17 00:00:00 2001 From: s3onghyun Date: Thu, 18 Jun 2026 16:03:12 +0900 Subject: [PATCH 365/571] docs: fix typo in helm version command description Signed-off-by: s3onghyun --- pkg/cmd/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/version.go b/pkg/cmd/version.go index 80fb0d712..1099d46ea 100644 --- a/pkg/cmd/version.go +++ b/pkg/cmd/version.go @@ -30,7 +30,7 @@ import ( const versionDesc = ` Show the version for Helm. -This will print a representation the version of Helm. +This will print a representation of the version of Helm. The output will look something like this: version.BuildInfo{Version:"v3.2.1", GitCommit:"fe51cd1e31e6a202cba7dead9552a6d418ded79a", GitTreeState:"clean", GoVersion:"go1.13.10"} From c36e4d52bdf92a05a8d2d9f40e5927a1245dce2d Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Thu, 18 Jun 2026 07:56:32 -0600 Subject: [PATCH 366/571] test: use assert.Empty for zero-length assertions in validate_test Signed-off-by: Terry Howe --- pkg/action/validate_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go index 8e983e9ff..3624d3676 100644 --- a/pkg/action/validate_test.go +++ b/pkg/action/validate_test.go @@ -237,7 +237,7 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) assert.NoError(t, err) assert.Len(t, ownedList, 2) - assert.Len(t, unownedList, 0) + assert.Empty(t, unownedList) }) // Test mix of owned and unowned resources @@ -261,8 +261,8 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) assert.NoError(t, err) - assert.Len(t, ownedList, 0) - assert.Len(t, unownedList, 0) + assert.Empty(t, ownedList) + assert.Empty(t, unownedList) }) // Test resource with no ownership metadata @@ -272,7 +272,7 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) assert.NoError(t, err) - assert.Len(t, ownedList, 0) + assert.Empty(t, ownedList) assert.Len(t, unownedList, 1) }) @@ -283,7 +283,7 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) assert.NoError(t, err) - assert.Len(t, ownedList, 0) + assert.Empty(t, ownedList) assert.Len(t, unownedList, 1) }) From 5848047c6b1e854fe7d451e451dbf748225232b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:32:39 +0000 Subject: [PATCH 367/571] chore(deps): bump actions/checkout from 6.0.3 to 7.0.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.3 to 7.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/df4cb1c069e1874edd31b4311f1884172cec0e10...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index f12ca5f00..a0fea406c 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # pin@v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 38aa0a8ca..323b8ec5b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -44,7 +44,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # pin@v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index eaf36d257..13ea569ef 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # pin@v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 9ca2f643f..ba3714eae 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # pin@v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 with: persist-credentials: false - name: Add variables to environment file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 99c5ddcd1..993c45899 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest-16-cores steps: - name: Checkout source code - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # pin@v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 with: fetch-depth: 0 @@ -85,7 +85,7 @@ jobs: if: github.ref == 'refs/heads/main' && github.repository == 'helm/helm' steps: - name: Checkout source code - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # pin@v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 with: fetch-depth: 0 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index b7a27a64a..03419cbda 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -28,7 +28,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false From a0c7d1e1f5ef222fb1be143e9846cebf8b3a2d54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:33:12 +0000 Subject: [PATCH 368/571] chore(deps): bump github.com/cyphar/filepath-securejoin Bumps [github.com/cyphar/filepath-securejoin](https://github.com/cyphar/filepath-securejoin) from 0.6.1 to 0.7.0. - [Release notes](https://github.com/cyphar/filepath-securejoin/releases) - [Changelog](https://github.com/cyphar/filepath-securejoin/blob/main/CHANGELOG.md) - [Commits](https://github.com/cyphar/filepath-securejoin/compare/v0.6.1...v0.7.0) --- updated-dependencies: - dependency-name: github.com/cyphar/filepath-securejoin dependency-version: 0.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 72a708cf3..ea6d6c972 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/Masterminds/vcs v1.13.3 github.com/ProtonMail/go-crypto v1.4.1 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 - github.com/cyphar/filepath-securejoin v0.6.1 + github.com/cyphar/filepath-securejoin v0.7.0 github.com/distribution/distribution/v3 v3.1.1 github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 diff --git a/go.sum b/go.sum index b5a6777a6..f50611b85 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= -github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE= +github.com/cyphar/filepath-securejoin v0.7.0/go.mod h1:ymLGms/u3BYaviIiuKFnUx8EkQEZeK6cInNoAPJA3o4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= From 34cfdfdbe57ec9690113e141395c499a7c00ca6b Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Wed, 25 Feb 2026 08:01:45 +0100 Subject: [PATCH 369/571] chore: enable contextcheck, fatcontext and noctx linters Signed-off-by: Matthieu MOREL --- .golangci.yml | 3 + cmd/helm/helm_test.go | 2 +- internal/chart/v3/lint/rules/template.go | 3 +- internal/monocular/search.go | 10 ++- internal/monocular/search_test.go | 2 +- internal/plugin/installer/installer.go | 8 +- internal/plugin/runtime_extismv1_test.go | 2 +- internal/plugin/runtime_subprocess.go | 21 ++--- internal/plugin/runtime_subprocess_getter.go | 6 +- pkg/action/action.go | 7 +- pkg/action/action_test.go | 28 +++---- pkg/action/install.go | 2 +- pkg/action/upgrade.go | 6 +- pkg/chart/common/util/jsonschema.go | 3 +- pkg/chart/v2/lint/rules/template.go | 3 +- pkg/cmd/search_hub.go | 9 +- pkg/engine/engine.go | 36 ++++++-- pkg/engine/engine_test.go | 12 +-- pkg/engine/lookup_func.go | 8 +- pkg/getter/httpgetter.go | 3 +- pkg/kube/ready.go | 2 +- pkg/kubeenv/roundtripper_test.go | 9 +- pkg/registry/registry_test.go | 8 +- pkg/repo/v1/repotest/server.go | 3 +- pkg/repo/v1/repotest/server_test.go | 87 ++++++++------------ 25 files changed, 161 insertions(+), 122 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index e7c8b11a6..b3ced6f1a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -20,10 +20,12 @@ linters: # Keep sorted alphabetically enable: - bidichk + - contextcheck - depguard - dupl - errorlint - exhaustive + - fatcontext - gocritic - gomodguard - govet @@ -31,6 +33,7 @@ linters: - misspell - modernize - nakedret + - noctx - nolintlint - perfsprint - revive diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index a9362f772..95f6504c1 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -46,7 +46,7 @@ func TestCliPluginExitCode(t *testing.T) { // So that the second run is able to run main() and this first run can verify the exit status returned by that. // // This technique originates from https://talks.golang.org/2014/testing.slide#23. - cmd := exec.Command(os.Args[0], "-test.run=TestCliPluginExitCode") + cmd := exec.CommandContext(t.Context(), os.Args[0], "-test.run=TestCliPluginExitCode") cmd.Env = append( os.Environ(), "RUN_MAIN_FOR_TESTING=1", diff --git a/internal/chart/v3/lint/rules/template.go b/internal/chart/v3/lint/rules/template.go index a8ae910eb..464acc769 100644 --- a/internal/chart/v3/lint/rules/template.go +++ b/internal/chart/v3/lint/rules/template.go @@ -19,6 +19,7 @@ package rules import ( "bufio" "bytes" + "context" "errors" "fmt" "io" @@ -104,7 +105,7 @@ func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string } var e engine.Engine e.LintMode = true - renderedContentMap, err := e.Render(chart, valuesToRender) + renderedContentMap, err := e.RenderWithContext(context.Background(), chart, valuesToRender) renderOk := linter.RunLinterRule(support.ErrorSev, fpath, err) diff --git a/internal/monocular/search.go b/internal/monocular/search.go index cfae87ded..29cc7f1a4 100644 --- a/internal/monocular/search.go +++ b/internal/monocular/search.go @@ -17,6 +17,7 @@ limitations under the License. package monocular import ( + "context" "encoding/json" "fmt" "net/http" @@ -98,7 +99,14 @@ type ChartVersion struct { } // Search performs a search against the monocular search API +// +// Deprecated: Use SearchWithContext instead. func (c *Client) Search(term string) ([]SearchResult, error) { + return c.SearchWithContext(context.Background(), term) +} + +// SearchWithContext performs a search against the monocular search API +func (c *Client) SearchWithContext(ctx context.Context, term string) ([]SearchResult, error) { // Create the URL to the search endpoint // Note, this is currently an internal API for the Hub. This should be // formatted without showing how monocular operates. @@ -113,7 +121,7 @@ func (c *Client) Search(term string) ([]SearchResult, error) { p.RawQuery = "q=" + url.QueryEscape(term) // Create request - req, err := http.NewRequest(http.MethodGet, p.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.String(), http.NoBody) if err != nil { return nil, err } diff --git a/internal/monocular/search_test.go b/internal/monocular/search_test.go index cdf67f69e..e4475f24e 100644 --- a/internal/monocular/search_test.go +++ b/internal/monocular/search_test.go @@ -37,7 +37,7 @@ func TestSearch(t *testing.T) { t.Errorf("unable to create monocular client: %s", err) } - results, err := c.Search("phpmyadmin") + results, err := c.SearchWithContext(t.Context(), "phpmyadmin") if err != nil { t.Errorf("unable to search monocular: %s", err) } diff --git a/internal/plugin/installer/installer.go b/internal/plugin/installer/installer.go index f73197629..a7ca35ad4 100644 --- a/internal/plugin/installer/installer.go +++ b/internal/plugin/installer/installer.go @@ -16,6 +16,7 @@ limitations under the License. package installer import ( + "context" "errors" "fmt" "log/slog" @@ -189,12 +190,17 @@ func isRemoteHTTPArchive(source string) bool { } // If no suffix match, try HEAD request to check content type - res, err := http.Head(source) + req, err := http.NewRequestWithContext(context.Background(), http.MethodHead, source, http.NoBody) if err != nil { // If we get an error at the network layer, we can't install it. So // we return false. return false } + res, err := http.DefaultClient.Do(req) + if err != nil { + return false + } + defer res.Body.Close() // Next, we look for the content type or content disposition headers to see // if they have matching extractors. diff --git a/internal/plugin/runtime_extismv1_test.go b/internal/plugin/runtime_extismv1_test.go index bcd791905..9451c693b 100644 --- a/internal/plugin/runtime_extismv1_test.go +++ b/internal/plugin/runtime_extismv1_test.go @@ -46,7 +46,7 @@ func buildLoadExtismPlugin(t *testing.T, dir string) pluginRaw { require.NoError(t, err) require.Equal(t, "extism/v1", m.Runtime, "expected plugin runtime to be extism/v1") - cmd := exec.Command("make", "-C", dir) + cmd := exec.CommandContext(t.Context(), "make", "-C", dir) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr require.NoError(t, cmd.Run(), "failed to build plugin in %q", dir) diff --git a/internal/plugin/runtime_subprocess.go b/internal/plugin/runtime_subprocess.go index cd1a0842c..3c0af4a4e 100644 --- a/internal/plugin/runtime_subprocess.go +++ b/internal/plugin/runtime_subprocess.go @@ -96,14 +96,14 @@ func (r *SubprocessPluginRuntime) Metadata() Metadata { return r.metadata } -func (r *SubprocessPluginRuntime) Invoke(_ context.Context, input *Input) (*Output, error) { +func (r *SubprocessPluginRuntime) Invoke(ctx context.Context, input *Input) (*Output, error) { switch input.Message.(type) { case schema.InputMessageCLIV1: - return r.runCLI(input) + return r.runCLI(ctx, input) case schema.InputMessageGetterV1: - return r.runGetter(input) + return r.runGetter(ctx, input) case schema.InputMessagePostRendererV1: - return r.runPostrenderer(input) + return r.runPostrenderer(ctx, input) default: return nil, fmt.Errorf("unsupported subprocess plugin type %q", r.metadata.Type) } @@ -113,7 +113,7 @@ func (r *SubprocessPluginRuntime) Invoke(_ context.Context, input *Input) (*Outp // This method allows execution with different command/args than the plugin's default func (r *SubprocessPluginRuntime) InvokeWithEnv(main string, argv []string, env []string, stdin io.Reader, stdout, stderr io.Writer) error { mainCmdExp := os.ExpandEnv(main) - cmd := exec.Command(mainCmdExp, argv...) + cmd := exec.CommandContext(context.Background(), mainCmdExp, argv...) cmd.Env = slices.Clone(os.Environ()) cmd.Env = append( cmd.Env, @@ -149,7 +149,7 @@ func (r *SubprocessPluginRuntime) InvokeHook(event string) error { return err } - cmd := exec.Command(main, argv...) + cmd := exec.CommandContext(context.Background(), main, argv...) cmd.Env = FormatEnv(env) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -189,7 +189,7 @@ func executeCmd(prog *exec.Cmd, pluginName string) error { return nil } -func (r *SubprocessPluginRuntime) runCLI(input *Input) (*Output, error) { +func (r *SubprocessPluginRuntime) runCLI(ctx context.Context, input *Input) (*Output, error) { if _, ok := input.Message.(schema.InputMessageCLIV1); !ok { return nil, fmt.Errorf("plugin %q input message does not implement InputMessageCLIV1", r.metadata.Name) } @@ -209,7 +209,7 @@ func (r *SubprocessPluginRuntime) runCLI(input *Input) (*Output, error) { return nil, fmt.Errorf("failed to prepare plugin command: %w", err) } - cmd := exec.Command(command, args...) + cmd := exec.CommandContext(ctx, command, args...) cmd.Env = FormatEnv(env) cmd.Stdin = input.Stdin @@ -226,7 +226,7 @@ func (r *SubprocessPluginRuntime) runCLI(input *Input) (*Output, error) { }, nil } -func (r *SubprocessPluginRuntime) runPostrenderer(input *Input) (*Output, error) { +func (r *SubprocessPluginRuntime) runPostrenderer(ctx context.Context, input *Input) (*Output, error) { if _, ok := input.Message.(schema.InputMessagePostRendererV1); !ok { return nil, fmt.Errorf("plugin %q input message does not implement InputMessagePostRendererV1", r.metadata.Name) } @@ -244,7 +244,8 @@ func (r *SubprocessPluginRuntime) runPostrenderer(input *Input) (*Output, error) return nil, fmt.Errorf("failed to prepare plugin command: %w", err) } - cmd := exec.Command( + cmd := exec.CommandContext( + ctx, command, args...) diff --git a/internal/plugin/runtime_subprocess_getter.go b/internal/plugin/runtime_subprocess_getter.go index c7262e0dd..3f07fa764 100644 --- a/internal/plugin/runtime_subprocess_getter.go +++ b/internal/plugin/runtime_subprocess_getter.go @@ -17,6 +17,7 @@ package plugin import ( "bytes" + "context" "fmt" "log/slog" "maps" @@ -40,7 +41,7 @@ func getProtocolCommand(commands []SubprocessProtocolCommand, protocol string) * } // TODO can we replace a lot of this func with RuntimeSubprocess.invokeWithEnv? -func (r *SubprocessPluginRuntime) runGetter(input *Input) (*Output, error) { +func (r *SubprocessPluginRuntime) runGetter(ctx context.Context, input *Input) (*Output, error) { msg, ok := (input.Message).(schema.InputMessageGetterV1) if !ok { return nil, fmt.Errorf("expected input type schema.InputMessageGetterV1, got %T", input) @@ -81,7 +82,8 @@ func (r *SubprocessPluginRuntime) runGetter(input *Input) (*Output, error) { buf := bytes.Buffer{} // subprocess getters are expected to write content to stdout pluginCommand := filepath.Join(r.pluginDir, command) - cmd := exec.Command( + cmd := exec.CommandContext( + ctx, pluginCommand, args...) cmd.Env = FormatEnv(env) diff --git a/pkg/action/action.go b/pkg/action/action.go index 4ed718371..6e5d8c15a 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -18,6 +18,7 @@ package action import ( "bytes" + "context" "errors" "fmt" "io" @@ -275,7 +276,7 @@ func splitAndDeannotate(postrendered, fallbackPrefix string) (map[string]string, // TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed // // This code has to do with writing files to disk. -func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrenderer.PostRenderer, interactWithRemote, enableDNS, hideSecret bool, postRenderStrategy PostRenderStrategy) ([]*release.Hook, *bytes.Buffer, string, error) { +func (cfg *Configuration) renderResources(ctx context.Context, ch *chart.Chart, values common.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrenderer.PostRenderer, interactWithRemote, enableDNS, hideSecret bool, postRenderStrategy PostRenderStrategy) ([]*release.Hook, *bytes.Buffer, string, error) { var hs []*release.Hook b := bytes.NewBuffer(nil) @@ -305,13 +306,13 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values common.Values, e.EnableDNS = enableDNS e.CustomTemplateFuncs = cfg.CustomTemplateFuncs - files, err2 = e.Render(ch, values) + files, err2 = e.RenderWithContext(ctx, ch, values) } else { var e engine.Engine e.EnableDNS = enableDNS e.CustomTemplateFuncs = cfg.CustomTemplateFuncs - files, err2 = e.Render(ch, values) + files, err2 = e.RenderWithContext(ctx, ch, values) } if err2 != nil { diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index 6a561985f..ecf160500 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -1823,7 +1823,7 @@ func TestRenderResources_PostRenderer_Success(t *testing.T) { values := map[string]any{} hooks, buf, notes, err := cfg.renderResources( - ch, values, "test-release", "", false, false, false, + t.Context(), ch, values, "test-release", "", false, false, false, mockPR, false, false, false, PostRenderStrategyCombined, ) @@ -1870,7 +1870,7 @@ func TestRenderResources_PostRenderer_Error(t *testing.T) { values := map[string]any{} _, _, _, err := cfg.renderResources( - ch, values, "test-release", "", false, false, false, + t.Context(), ch, values, "test-release", "", false, false, false, mockPR, false, false, false, PostRenderStrategyCombined, ) @@ -1898,7 +1898,7 @@ func TestRenderResources_PostRenderer_MergeError(t *testing.T) { values := map[string]any{} _, _, _, err := cfg.renderResources( - ch, values, "test-release", "", false, false, false, + t.Context(), ch, values, "test-release", "", false, false, false, mockPR, false, false, false, PostRenderStrategyCombined, ) @@ -1920,7 +1920,7 @@ func TestRenderResources_PostRenderer_SplitError(t *testing.T) { values := map[string]any{} _, _, _, err := cfg.renderResources( - ch, values, "test-release", "", false, false, false, + t.Context(), ch, values, "test-release", "", false, false, false, mockPR, false, false, false, PostRenderStrategyCombined, ) @@ -1941,7 +1941,7 @@ func TestRenderResources_PostRenderer_Integration(t *testing.T) { values := map[string]any{} hooks, buf, notes, err := cfg.renderResources( - ch, values, "test-release", "", false, false, false, + t.Context(), ch, values, "test-release", "", false, false, false, mockPR, false, false, false, PostRenderStrategyCombined, ) @@ -1980,7 +1980,7 @@ func TestRenderResources_NoPostRenderer(t *testing.T) { values := map[string]any{} hooks, buf, notes, err := cfg.renderResources( - ch, values, "test-release", "", false, false, false, + t.Context(), ch, values, "test-release", "", false, false, false, nil, false, false, false, PostRenderStrategyCombined, ) @@ -2041,7 +2041,7 @@ spec: } hooks, buf, _, err := cfg.renderResources( - ch, nil, "test-release", "", false, false, false, + t.Context(), ch, nil, "test-release", "", false, false, false, mockPR, false, false, false, PostRenderStrategySeparate, ) @@ -2083,7 +2083,7 @@ metadata: } _, _, _, err := cfg.renderResources( - ch, nil, "test-release", "", false, false, false, + t.Context(), ch, nil, "test-release", "", false, false, false, mockPR, false, false, false, PostRenderStrategyCombined, ) @@ -2119,7 +2119,7 @@ metadata: } _, _, _, err := cfg.renderResources( - ch, nil, "test-release", "", false, false, false, + t.Context(), ch, nil, "test-release", "", false, false, false, mockPR, false, false, false, PostRenderStrategy(""), ) @@ -2153,7 +2153,7 @@ metadata: } _, _, _, err := cfg.renderResources( - ch, nil, "test-release", "", false, false, false, + t.Context(), ch, nil, "test-release", "", false, false, false, mockPR, false, false, false, PostRenderStrategySeparate, ) @@ -2187,7 +2187,7 @@ metadata: } _, _, _, err := cfg.renderResources( - ch, nil, "test-release", "", false, false, false, + t.Context(), ch, nil, "test-release", "", false, false, false, mockPR, false, false, false, PostRenderStrategySeparate, ) @@ -2221,7 +2221,7 @@ metadata: } hooks, manifestDoc, _, err := cfg.renderResources( - ch, nil, "test-release", "", false, false, false, + t.Context(), ch, nil, "test-release", "", false, false, false, mockPR, false, false, false, PostRenderStrategyNoHooks, ) @@ -2258,7 +2258,7 @@ metadata: } _, _, _, err := cfg.renderResources( - ch, nil, "test-release", "", false, false, false, + t.Context(), ch, nil, "test-release", "", false, false, false, mockPR, false, false, false, PostRenderStrategyNoHooks, ) @@ -2280,7 +2280,7 @@ metadata: mockPR := &mockPostRenderer{} _, _, _, err := cfg.renderResources( - ch, nil, "test-release", "", false, false, false, + t.Context(), ch, nil, "test-release", "", false, false, false, mockPR, false, false, false, PostRenderStrategy("bogus"), ) diff --git a/pkg/action/install.go b/pkg/action/install.go index 580b8a0cb..fcfae7214 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -375,7 +375,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st rel := i.createRelease(chrt, vals, i.Labels) var manifestDoc *bytes.Buffer - rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithServer(i.DryRunStrategy), i.EnableDNS, i.HideSecret, i.PostRenderStrategy) + rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(ctx, chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithServer(i.DryRunStrategy), i.EnableDNS, i.HideSecret, i.PostRenderStrategy) // Even for errors, attach this if available if manifestDoc != nil { rel.Manifest = manifestDoc.String() diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 0d7b1b148..81d51164f 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -193,7 +193,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Char } u.cfg.Logger().Debug("preparing upgrade", "name", name) - currentRelease, upgradedRelease, serverSideApply, err := u.prepareUpgrade(name, chrt, vals) + currentRelease, upgradedRelease, serverSideApply, err := u.prepareUpgrade(ctx, name, chrt, vals) if err != nil { return nil, err } @@ -218,7 +218,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Char } // prepareUpgrade builds an upgraded release for an upgrade operation. -func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[string]any) (*release.Release, *release.Release, bool, error) { +func (u *Upgrade) prepareUpgrade(ctx context.Context, name string, chart *chartv2.Chart, vals map[string]any) (*release.Release, *release.Release, bool, error) { if chart == nil { return nil, nil, false, errMissingChart } @@ -300,7 +300,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str return nil, nil, false, err } - hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithServer(u.DryRunStrategy), u.EnableDNS, u.HideSecret, u.PostRenderStrategy) + hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(ctx, chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithServer(u.DryRunStrategy), u.EnableDNS, u.HideSecret, u.PostRenderStrategy) if err != nil { return nil, nil, false, err } diff --git a/pkg/chart/common/util/jsonschema.go b/pkg/chart/common/util/jsonschema.go index 63ca0c274..624143993 100644 --- a/pkg/chart/common/util/jsonschema.go +++ b/pkg/chart/common/util/jsonschema.go @@ -18,6 +18,7 @@ package util import ( "bytes" + "context" "crypto/tls" "errors" "fmt" @@ -41,7 +42,7 @@ type HTTPURLLoader http.Client func (l *HTTPURLLoader) Load(urlStr string) (any, error) { client := (*http.Client)(l) - req, err := http.NewRequest(http.MethodGet, urlStr, nil) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, urlStr, http.NoBody) if err != nil { return nil, fmt.Errorf("failed to create HTTP request for %s: %w", urlStr, err) } diff --git a/pkg/chart/v2/lint/rules/template.go b/pkg/chart/v2/lint/rules/template.go index 25ed31900..47209112e 100644 --- a/pkg/chart/v2/lint/rules/template.go +++ b/pkg/chart/v2/lint/rules/template.go @@ -19,6 +19,7 @@ package rules import ( "bufio" "bytes" + "context" "errors" "fmt" "io" @@ -134,7 +135,7 @@ func (t *templateLinter) Lint() { } var e engine.Engine e.LintMode = true - renderedContentMap, err := e.Render(chart, valuesToRender) + renderedContentMap, err := e.RenderWithContext(context.Background(), chart, valuesToRender) renderOk := t.linter.RunLinterRule(support.ErrorSev, templatesDir, err) diff --git a/pkg/cmd/search_hub.go b/pkg/cmd/search_hub.go index 3081961be..3f7afa3f4 100644 --- a/pkg/cmd/search_hub.go +++ b/pkg/cmd/search_hub.go @@ -17,6 +17,7 @@ limitations under the License. package cmd import ( + "context" "errors" "fmt" "io" @@ -65,8 +66,8 @@ func newSearchHubCmd(out io.Writer) *cobra.Command { Use: "hub [KEYWORD]", Short: "search for charts in the Artifact Hub or your own hub instance", Long: searchHubDesc, - RunE: func(_ *cobra.Command, args []string) error { - return o.run(out, args) + RunE: func(c *cobra.Command, args []string) error { + return o.run(c.Context(), out, args) }, } @@ -81,14 +82,14 @@ func newSearchHubCmd(out io.Writer) *cobra.Command { return cmd } -func (o *searchHubOptions) run(out io.Writer, args []string) error { +func (o *searchHubOptions) run(ctx context.Context, out io.Writer, args []string) error { c, err := monocular.New(o.searchEndpoint) if err != nil { return fmt.Errorf("unable to create connection to %q: %w", o.searchEndpoint, err) } q := strings.Join(args, " ") - results, err := c.Search(q) + results, err := c.SearchWithContext(ctx, q) if err != nil { slog.Debug("search failed", slog.Any("error", err)) return fmt.Errorf("unable to perform search against %q", o.searchEndpoint) diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 6fd2beed8..4f7fa4590 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -17,6 +17,7 @@ limitations under the License. package engine import ( + "context" "errors" "fmt" "log/slog" @@ -76,9 +77,34 @@ func New(config *rest.Config) Engine { // that section of the values will be passed into the "foo" chart. And if that // section contains a value named "bar", that value will be passed on to the // bar chart during render time. +// +// Deprecated: Use RenderWithContext instead. func (e Engine) Render(chrt ci.Charter, values common.Values) (map[string]string, error) { + return e.RenderWithContext(context.Background(), chrt, values) +} + +// RenderWithContext takes a chart, optional values, and value overrides, and attempts to render the Go templates. +// +// RenderWithContext can be called repeatedly on the same engine. +// +// This will look in the chart's 'templates' data (e.g. the 'templates/' directory) +// and attempt to render the templates there using the values passed in. +// +// Values are scoped to their templates. A dependency template will not have +// access to the values set for its parent. If chart "foo" includes chart "bar", +// "bar" will not have access to the values for "foo". +// +// Values should be prepared with something like `chartutils.ReadValues`. +// +// Values are passed through the templates according to scope. If the top layer +// chart includes the chart foo, which includes the chart bar, the values map +// will be examined for a table called "foo". If "foo" is found in vals, +// that section of the values will be passed into the "foo" chart. And if that +// section contains a value named "bar", that value will be passed on to the +// bar chart during render time. +func (e Engine) RenderWithContext(ctx context.Context, chrt ci.Charter, values common.Values) (map[string]string, error) { tmap := allTemplates(chrt, values) - return e.render(tmap) + return e.render(ctx, tmap) } // Render takes a chart, optional values, and value overrides, and attempts to @@ -195,7 +221,7 @@ func tplFun(parent *template.Template, includedNames map[string]int, strict bool } // initFunMap creates the Engine's FuncMap and adds context-specific functions. -func (e Engine) initFunMap(t *template.Template) { +func (e Engine) initFunMap(ctx context.Context, t *template.Template) { funcMap := funcMap() includedNames := make(map[string]int) @@ -238,7 +264,7 @@ func (e Engine) initFunMap(t *template.Template) { // If we are not linting and have a cluster connection, provide a Kubernetes-backed // implementation. if !e.LintMode && e.clientProvider != nil { - funcMap["lookup"] = newLookupFunction(*e.clientProvider) + funcMap["lookup"] = newLookupFunction(ctx, *e.clientProvider) } // When DNS lookups are not enabled override the sprig function and return @@ -256,7 +282,7 @@ func (e Engine) initFunMap(t *template.Template) { } // render takes a map of templates/values and renders them. -func (e Engine) render(tpls map[string]renderable) (rendered map[string]string, err error) { +func (e Engine) render(ctx context.Context, tpls map[string]renderable) (rendered map[string]string, err error) { // Basically, what we do here is start with an empty parent template and then // build up a list of templates -- one for each file. Once all of the templates // have been parsed, we loop through again and execute every template. @@ -278,7 +304,7 @@ func (e Engine) render(tpls map[string]renderable) (rendered map[string]string, t.Option("missingkey=zero") } - e.initFunMap(t) + e.initFunMap(ctx, t) // We want to parse the templates in a predictable order. The order favors // higher-level (in file system) templates over deeply nested templates. diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 612ab85f0..f666b63a6 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -197,7 +197,7 @@ func TestRenderInternals(t *testing.T) { "three": {tpl: `{{template "two" dict "Value" "three"}}`, vals: vals}, } - out, err := new(Engine).render(tpls) + out, err := new(Engine).render(t.Context(), tpls) if err != nil { t.Fatalf("Failed template rendering: %s", err) } @@ -443,7 +443,7 @@ func TestParallelRenderInternals(t *testing.T) { vals: map[string]any{"val": tt}, }, } - out, err := e.render(tpls) + out, err := e.render(t.Context(), tpls) if err != nil { t.Errorf("Failed to render %s: %s", tt, err) } @@ -462,7 +462,7 @@ func TestParseErrors(t *testing.T) { tplsUndefinedFunction := map[string]renderable{ "undefined_function": {tpl: `{{foo}}`, vals: vals}, } - _, err := new(Engine).render(tplsUndefinedFunction) + _, err := new(Engine).render(t.Context(), tplsUndefinedFunction) if err == nil { t.Fatalf("Expected failures while rendering: %s", err) } @@ -525,7 +525,7 @@ linebreak`, for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { - _, err := new(Engine).render(tt.tpls) + _, err := new(Engine).render(t.Context(), tt.tpls) if err == nil { t.Fatalf("Expected failures while rendering: %s", err) } @@ -543,7 +543,7 @@ func TestFailErrors(t *testing.T) { tplsFailed := map[string]renderable{ "failtpl": {tpl: failtpl, vals: vals}, } - _, err := new(Engine).render(tplsFailed) + _, err := new(Engine).render(t.Context(), tplsFailed) if err == nil { t.Fatalf("Expected failures while rendering: %s", err) } @@ -554,7 +554,7 @@ func TestFailErrors(t *testing.T) { var e Engine e.LintMode = true - out, err := e.render(tplsFailed) + out, err := e.render(t.Context(), tplsFailed) if err != nil { t.Fatal(err) } diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go index c5c441c14..b0d0224be 100644 --- a/pkg/engine/lookup_func.go +++ b/pkg/engine/lookup_func.go @@ -36,7 +36,7 @@ type lookupFunc = func(apiversion string, resource string, namespace string, nam // // If the resource does not exist, no error is raised. func NewLookupFunction(config *rest.Config) lookupFunc { //nolint:revive - return newLookupFunction(clientProviderFromConfig{config: config}) + return newLookupFunction(context.Background(), clientProviderFromConfig{config: config}) } type ClientProvider interface { @@ -54,7 +54,7 @@ func (c clientProviderFromConfig) GetClientFor(apiVersion, kind string) (dynamic return getDynamicClientOnKind(apiVersion, kind, c.config) } -func newLookupFunction(clientProvider ClientProvider) lookupFunc { +func newLookupFunction(ctx context.Context, clientProvider ClientProvider) lookupFunc { return func(apiversion string, kind string, namespace string, name string) (map[string]any, error) { var client dynamic.ResourceInterface c, namespaced, err := clientProvider.GetClientFor(apiversion, kind) @@ -68,7 +68,7 @@ func newLookupFunction(clientProvider ClientProvider) lookupFunc { } if name != "" { // this will return a single object - obj, err := client.Get(context.Background(), name, metav1.GetOptions{}) + obj, err := client.Get(ctx, name, metav1.GetOptions{}) if err != nil { if apierrors.IsNotFound(err) { // Just return an empty interface when the object was not found. @@ -86,7 +86,7 @@ func newLookupFunction(clientProvider ClientProvider) lookupFunc { return obj.UnstructuredContent(), nil } // this will return a list - obj, err := client.List(context.Background(), metav1.ListOptions{}) + obj, err := client.List(ctx, metav1.ListOptions{}) if err != nil { if apierrors.IsNotFound(err) { // Just return an empty interface when the object was not found. diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go index 2eb2d5d8c..a72a9337a 100644 --- a/pkg/getter/httpgetter.go +++ b/pkg/getter/httpgetter.go @@ -17,6 +17,7 @@ package getter import ( "bytes" + "context" "crypto/tls" "fmt" "io" @@ -49,7 +50,7 @@ func (g *HTTPGetter) Get(href string, options ...Option) (*bytes.Buffer, error) func (g *HTTPGetter) get(href string, opts getterOptions) (*bytes.Buffer, error) { // Set a helm specific user agent so that a repo server and metrics can // separate helm calls from other tools interacting with repos. - req, err := http.NewRequest(http.MethodGet, href, nil) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, href, http.NoBody) if err != nil { return nil, err } diff --git a/pkg/kube/ready.go b/pkg/kube/ready.go index a1a3d4a9a..196fe922a 100644 --- a/pkg/kube/ready.go +++ b/pkg/kube/ready.go @@ -108,7 +108,7 @@ func (c *ReadyChecker) IsReady(ctx context.Context, v *resource.Info) (bool, err return c.pausedAsReady, nil } // Find RS associated with deployment - newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, c.client.AppsV1()) + newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, c.client.AppsV1()) //nolint:contextcheck if err != nil || newReplicaSet == nil { return false, err } diff --git a/pkg/kubeenv/roundtripper_test.go b/pkg/kubeenv/roundtripper_test.go index b921eac82..6f0caf6f8 100644 --- a/pkg/kubeenv/roundtripper_test.go +++ b/pkg/kubeenv/roundtripper_test.go @@ -25,6 +25,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type fakeRoundTripper struct { @@ -134,15 +135,15 @@ func TestRetryingRoundTripper_RoundTrip(t *testing.T) { rt := RetryingRoundTripper{ Wrapped: fakeRT, } - req, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://example.com", http.NoBody) + require.NoError(t, err) resp, err := rt.RoundTrip(req) if tt.expectedErr != "" { - assert.Error(t, err) - assert.Contains(t, err.Error(), tt.expectedErr) + require.ErrorContains(t, err, tt.expectedErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.expectedCode, resp.StatusCode) assert.Equal(t, tt.expectedCalls, fakeRT.calls) diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index 3fc08a900..dc8118c6e 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -18,7 +18,6 @@ package registry import ( "bytes" - "context" "crypto/tls" "fmt" "io" @@ -125,7 +124,8 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool) { // Registry config config := &configuration.Configuration{} - ln, err := net.Listen("tcp", "127.0.0.1:0") + lnCfg := net.ListenConfig{} + ln, err := lnCfg.Listen(suite.T().Context(), "tcp", "127.0.0.1:0") suite.Require().NoError(err, "no error finding free port for test registry") defer func() { _ = ln.Close() }() @@ -158,7 +158,7 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool) { config.HTTP.TLS.ClientCAs = []string{tlsCA} } } - suite.dockerRegistry, err = registry.NewRegistry(context.Background(), config) + suite.dockerRegistry, err = registry.NewRegistry(suite.T().Context(), config) suite.Require().NoError(err, "no error creating test registry") suite.FakeRegistryHost = initFakeRegistryTestServer() @@ -170,7 +170,7 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool) { func teardown(suite *TestRegistry) { if suite.dockerRegistry != nil { - _ = suite.dockerRegistry.Shutdown(context.Background()) + _ = suite.dockerRegistry.Shutdown(suite.T().Context()) } } diff --git a/pkg/repo/v1/repotest/server.go b/pkg/repo/v1/repotest/server.go index f1b5e0744..61dcd6931 100644 --- a/pkg/repo/v1/repotest/server.go +++ b/pkg/repo/v1/repotest/server.go @@ -180,7 +180,8 @@ func NewOCIServer(t *testing.T, dir string) (*OCIServer, error) { // Registry config config := &configuration.Configuration{} - ln, err := net.Listen("tcp", "127.0.0.1:0") + lnCfg := net.ListenConfig{} + ln, err := lnCfg.Listen(t.Context(), "tcp", "127.0.0.1:0") if err != nil { t.Fatalf("error finding free port for test registry: %v", err) } diff --git a/pkg/repo/v1/repotest/server_test.go b/pkg/repo/v1/repotest/server_test.go index 499091a57..1364fc9c9 100644 --- a/pkg/repo/v1/repotest/server_test.go +++ b/pkg/repo/v1/repotest/server_test.go @@ -22,6 +22,7 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" "sigs.k8s.io/yaml" "helm.sh/helm/v4/internal/test/ensure" @@ -52,26 +53,24 @@ func TestServer(t *testing.T) { t.Errorf("Unexpected chart: %s", c[0]) } - res, err := http.Get(srv.URL() + "/examplechart-0.1.0.tgz") + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL()+"/examplechart-0.1.0.tgz", http.NoBody) + require.NoError(t, err) + client := http.DefaultClient + res, err := client.Do(req) + require.NoError(t, err) res.Body.Close() - if err != nil { - t.Fatal(err) - } if res.ContentLength < 500 { t.Errorf("Expected at least 500 bytes of data, got %d", res.ContentLength) } - res, err = http.Get(srv.URL() + "/index.yaml") - if err != nil { - t.Fatal(err) - } - + req, err = http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL()+"/index.yaml", http.NoBody) + require.NoError(t, err) + res, err = client.Do(req) + require.NoError(t, err) data, err := io.ReadAll(res.Body) res.Body.Close() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) m := repo.NewIndexFile() if err := yaml.Unmarshal(data, m); err != nil { @@ -87,11 +86,11 @@ func TestServer(t *testing.T) { t.Errorf("missing %q", expect) } - res, err = http.Get(srv.URL() + "/index.yaml-nosuchthing") + req, err = http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL()+"/index.yaml-nosuchthing", http.NoBody) + require.NoError(t, err) + res, err = client.Do(req) + require.NoError(t, err) res.Body.Close() - if err != nil { - t.Fatal(err) - } if res.StatusCode != http.StatusNotFound { t.Fatalf("Expected 404, got %d", res.StatusCode) } @@ -133,70 +132,56 @@ func TestNewTempServer(t *testing.T) { client := srv.Client() { - res, err := client.Head(srv.URL() + "/repositories.yaml") - if err != nil { - t.Error(err) - } - + req, err := http.NewRequestWithContext(t.Context(), http.MethodHead, srv.URL()+"/repositories.yaml", http.NoBody) + require.NoError(t, err) + res, err := client.Do(req) + require.NoError(t, err) res.Body.Close() - if res.StatusCode != http.StatusOK { t.Errorf("Expected 200, got %d", res.StatusCode) } } - { - res, err := client.Head(srv.URL() + "/examplechart-0.1.0.tgz") - if err != nil { - t.Error(err) - } + req, err := http.NewRequestWithContext(t.Context(), http.MethodHead, srv.URL()+"/examplechart-0.1.0.tgz", http.NoBody) + require.NoError(t, err) + res, err := client.Do(req) + require.NoError(t, err) res.Body.Close() - if res.StatusCode != http.StatusOK { t.Errorf("Expected 200, got %d", res.StatusCode) } } - - res, err := client.Get(srv.URL() + "/examplechart-0.1.0.tgz") + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL()+"/examplechart-0.1.0.tgz", http.NoBody) + require.NoError(t, err) + res, err := client.Do(req) + require.NoError(t, err) res.Body.Close() - if err != nil { - t.Fatal(err) - } - if res.ContentLength < 500 { t.Errorf("Expected at least 500 bytes of data, got %d", res.ContentLength) } - - res, err = client.Get(srv.URL() + "/index.yaml") - if err != nil { - t.Fatal(err) - } - + req, err = http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL()+"/index.yaml", http.NoBody) + require.NoError(t, err) + res, err = client.Do(req) + require.NoError(t, err) data, err := io.ReadAll(res.Body) res.Body.Close() - if err != nil { - t.Fatal(err) - } - + require.NoError(t, err) m := repo.NewIndexFile() if err := yaml.Unmarshal(data, m); err != nil { t.Fatal(err) } - if l := len(m.Entries); l != 1 { t.Fatalf("Expected 1 entry, got %d", l) } - expect := "examplechart" if !m.Has(expect, "0.1.0") { t.Errorf("missing %q", expect) } - - res, err = client.Get(srv.URL() + "/index.yaml-nosuchthing") + req, err = http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL()+"/index.yaml-nosuchthing", http.NoBody) + require.NoError(t, err) + res, err = client.Do(req) + require.NoError(t, err) res.Body.Close() - if err != nil { - t.Fatal(err) - } if res.StatusCode != http.StatusNotFound { t.Fatalf("Expected 404, got %d", res.StatusCode) } From b42faba9d293d45d2654dac5b540adb5b50e5b4f Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Tue, 16 Jun 2026 15:15:19 +1200 Subject: [PATCH 370/571] refactor(repotest): convert tests to testify assert/require Replace native Go testing patterns (t.Errorf, t.Fatalf, t.Error, t.Fatal) with github.com/stretchr/testify equivalents (assert.X, require.X) for improved test readability and error messages. Signed-off-by: George Jenkins --- pkg/repo/v1/repotest/server.go | 76 +++++++++-------------------- pkg/repo/v1/repotest/server_test.go | 70 +++++++------------------- 2 files changed, 40 insertions(+), 106 deletions(-) diff --git a/pkg/repo/v1/repotest/server.go b/pkg/repo/v1/repotest/server.go index 61dcd6931..06cdc9172 100644 --- a/pkg/repo/v1/repotest/server.go +++ b/pkg/repo/v1/repotest/server.go @@ -30,6 +30,8 @@ import ( "github.com/distribution/distribution/v3/registry" _ "github.com/distribution/distribution/v3/registry/auth/htpasswd" // used for docker test registry _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" // used for docker test registry + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/crypto/bcrypt" "sigs.k8s.io/yaml" @@ -44,9 +46,8 @@ func BasicAuthMiddleware(t *testing.T) http.HandlerFunc { t.Helper() return http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { username, password, ok := r.BasicAuth() - if !ok || username != "username" || password != "password" { - t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) - } + assert.True(t, ok && username == "username" && password == "password", + "Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) }) } @@ -97,9 +98,8 @@ func NewTempServer(t *testing.T, options ...ServerOption) *Server { t.Cleanup(func() { os.RemoveAll(srv.docroot) }) if srv.chartSourceGlob != "" { - if _, err := srv.CopyCharts(srv.chartSourceGlob); err != nil { - t.Fatal(err) - } + _, err := srv.CopyCharts(srv.chartSourceGlob) + require.NoError(t, err) } return srv @@ -109,9 +109,7 @@ func NewTempServer(t *testing.T, options ...ServerOption) *Server { func newServer(t *testing.T, docroot string, options ...ServerOption) *Server { t.Helper() absdocroot, err := filepath.Abs(docroot) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) s := &Server{ docroot: absdocroot, @@ -131,9 +129,7 @@ func newServer(t *testing.T, docroot string, options ...ServerOption) *Server { s.start() // Add the testing repository as the only repo. Server must be started for the server's URL to be valid - if err := setTestingRepository(s.URL(), filepath.Join(s.docroot, "repositories.yaml")); err != nil { - t.Fatal(err) - } + require.NoError(t, setTestingRepository(s.URL(), filepath.Join(s.docroot, "repositories.yaml"))) return s } @@ -169,22 +165,16 @@ func NewOCIServer(t *testing.T, dir string) (*OCIServer, error) { testUsername, testPassword := "username", "password" pwBytes, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost) - if err != nil { - t.Fatal("error generating bcrypt password for test htpasswd file") - } + require.NoError(t, err, "error generating bcrypt password for test htpasswd file") htpasswdPath := filepath.Join(dir, testHtpasswdFileBasename) err = os.WriteFile(htpasswdPath, fmt.Appendf(nil, "%s:%s\n", testUsername, string(pwBytes)), 0o644) - if err != nil { - t.Fatal("error creating test htpasswd file") - } + require.NoError(t, err, "error creating test htpasswd file") // Registry config config := &configuration.Configuration{} lnCfg := net.ListenConfig{} ln, err := lnCfg.Listen(t.Context(), "tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("error finding free port for test registry: %v", err) - } + require.NoError(t, err, "error finding free port for test registry") defer ln.Close() port := ln.Addr().(*net.TCPAddr).Port @@ -201,9 +191,7 @@ func NewOCIServer(t *testing.T, dir string) (*OCIServer, error) { registryURL := fmt.Sprintf("localhost:%d", port) r, err := registry.NewRegistry(t.Context(), config) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return &OCIServer{ Registry: r, @@ -237,53 +225,37 @@ func (srv *OCIServer) RunWithReturn(t *testing.T, opts ...OCIServerOpt) *OCIServ ociRegistry.ClientOptWriter(os.Stdout), ociRegistry.ClientOptCredentialsFile(credentialsFile), ) - if err != nil { - t.Fatalf("error creating registry client: %v", err) - } + require.NoError(t, err, "error creating registry client") err = registryClient.Login( srv.RegistryURL, ociRegistry.LoginOptBasicAuth(srv.TestUsername, srv.TestPassword), ociRegistry.LoginOptInsecure(true), ociRegistry.LoginOptPlainText(true)) - if err != nil { - t.Fatalf("error logging into registry with good credentials: %v", err) - } + require.NoError(t, err, "error logging into registry with good credentials") ref := srv.RegistryURL + "/u/ocitestuser/oci-dependent-chart:0.1.0" err = chartutil.ExpandFile(srv.Dir, filepath.Join(srv.Dir, "oci-dependent-chart-0.1.0.tgz")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // valid chart ch, err := loader.LoadDir(filepath.Join(srv.Dir, "oci-dependent-chart")) - if err != nil { - t.Fatal("error loading chart") - } + require.NoError(t, err, "error loading chart") err = os.RemoveAll(filepath.Join(srv.Dir, "oci-dependent-chart")) - if err != nil { - t.Fatal("error removing chart before push") - } + require.NoError(t, err, "error removing chart before push") // save it back to disk.. absPath, err := chartutil.Save(ch, srv.Dir) - if err != nil { - t.Fatal("could not create chart archive") - } + require.NoError(t, err, "could not create chart archive") // load it into memory... contentBytes, err := os.ReadFile(absPath) - if err != nil { - t.Fatal("could not load chart into memory") - } + require.NoError(t, err, "could not load chart into memory") result, err := registryClient.Push(contentBytes, ref) - if err != nil { - t.Fatalf("error pushing dependent chart: %s", err) - } + require.NoError(t, err, "error pushing dependent chart") t.Logf("Manifest.Digest: %s, Manifest.Size: %d, "+ "Config.Digest: %s, Config.Size: %d, "+ "Chart.Digest: %s, Chart.Size: %d", @@ -306,14 +278,10 @@ func (srv *OCIServer) RunWithReturn(t *testing.T, opts ...OCIServerOpt) *OCIServ absPath = filepath.Join(srv.Dir, fmt.Sprintf("%s-%s.tgz", c.Metadata.Name, c.Metadata.Version)) contentBytes, err = os.ReadFile(absPath) - if err != nil { - t.Fatal("could not load chart into memory") - } + require.NoError(t, err, "could not load chart into memory") result, err = registryClient.Push(contentBytes, dependingRef) - if err != nil { - t.Fatalf("error pushing depending chart: %s", err) - } + require.NoError(t, err, "error pushing depending chart") t.Logf("Manifest.Digest: %s, Manifest.Size: %d, "+ "Config.Digest: %s, Config.Size: %d, "+ "Chart.Digest: %s, Chart.Size: %d", diff --git a/pkg/repo/v1/repotest/server_test.go b/pkg/repo/v1/repotest/server_test.go index 1364fc9c9..7008e2305 100644 --- a/pkg/repo/v1/repotest/server_test.go +++ b/pkg/repo/v1/repotest/server_test.go @@ -22,6 +22,7 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sigs.k8s.io/yaml" @@ -40,18 +41,11 @@ func TestServer(t *testing.T) { defer srv.Stop() c, err := srv.CopyCharts("testdata/*.tgz") - if err != nil { - // Some versions of Go don't correctly fire defer on Fatal. - t.Fatal(err) - } + require.NoError(t, err) - if len(c) != 1 { - t.Errorf("Unexpected chart count: %d", len(c)) - } + assert.Len(t, c, 1) - if filepath.Base(c[0]) != "examplechart-0.1.0.tgz" { - t.Errorf("Unexpected chart: %s", c[0]) - } + assert.Equal(t, "examplechart-0.1.0.tgz", filepath.Base(c[0])) req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL()+"/examplechart-0.1.0.tgz", http.NoBody) require.NoError(t, err) @@ -60,9 +54,7 @@ func TestServer(t *testing.T) { require.NoError(t, err) res.Body.Close() - if res.ContentLength < 500 { - t.Errorf("Expected at least 500 bytes of data, got %d", res.ContentLength) - } + assert.GreaterOrEqual(t, res.ContentLength, int64(500)) req, err = http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL()+"/index.yaml", http.NoBody) require.NoError(t, err) @@ -73,27 +65,19 @@ func TestServer(t *testing.T) { require.NoError(t, err) m := repo.NewIndexFile() - if err := yaml.Unmarshal(data, m); err != nil { - t.Fatal(err) - } + require.NoError(t, yaml.Unmarshal(data, m)) - if l := len(m.Entries); l != 1 { - t.Fatalf("Expected 1 entry, got %d", l) - } + require.Len(t, m.Entries, 1) expect := "examplechart" - if !m.Has(expect, "0.1.0") { - t.Errorf("missing %q", expect) - } + assert.True(t, m.Has(expect, "0.1.0"), "missing %q", expect) req, err = http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL()+"/index.yaml-nosuchthing", http.NoBody) require.NoError(t, err) res, err = client.Do(req) require.NoError(t, err) res.Body.Close() - if res.StatusCode != http.StatusNotFound { - t.Fatalf("Expected 404, got %d", res.StatusCode) - } + require.Equal(t, http.StatusNotFound, res.StatusCode) } func TestNewTempServer(t *testing.T) { @@ -125,9 +109,7 @@ func TestNewTempServer(t *testing.T) { ) defer srv.Stop() - if srv.srv.URL == "" { - t.Fatal("unstarted server") - } + require.NotEmpty(t, srv.srv.URL, "unstarted server") client := srv.Client() @@ -137,9 +119,7 @@ func TestNewTempServer(t *testing.T) { res, err := client.Do(req) require.NoError(t, err) res.Body.Close() - if res.StatusCode != http.StatusOK { - t.Errorf("Expected 200, got %d", res.StatusCode) - } + assert.Equal(t, http.StatusOK, res.StatusCode) } { req, err := http.NewRequestWithContext(t.Context(), http.MethodHead, srv.URL()+"/examplechart-0.1.0.tgz", http.NoBody) @@ -147,18 +127,14 @@ func TestNewTempServer(t *testing.T) { res, err := client.Do(req) require.NoError(t, err) res.Body.Close() - if res.StatusCode != http.StatusOK { - t.Errorf("Expected 200, got %d", res.StatusCode) - } + assert.Equal(t, http.StatusOK, res.StatusCode) } req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL()+"/examplechart-0.1.0.tgz", http.NoBody) require.NoError(t, err) res, err := client.Do(req) require.NoError(t, err) res.Body.Close() - if res.ContentLength < 500 { - t.Errorf("Expected at least 500 bytes of data, got %d", res.ContentLength) - } + assert.GreaterOrEqual(t, res.ContentLength, int64(500)) req, err = http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL()+"/index.yaml", http.NoBody) require.NoError(t, err) res, err = client.Do(req) @@ -167,24 +143,16 @@ func TestNewTempServer(t *testing.T) { res.Body.Close() require.NoError(t, err) m := repo.NewIndexFile() - if err := yaml.Unmarshal(data, m); err != nil { - t.Fatal(err) - } - if l := len(m.Entries); l != 1 { - t.Fatalf("Expected 1 entry, got %d", l) - } + require.NoError(t, yaml.Unmarshal(data, m)) + require.Len(t, m.Entries, 1) expect := "examplechart" - if !m.Has(expect, "0.1.0") { - t.Errorf("missing %q", expect) - } + assert.True(t, m.Has(expect, "0.1.0"), "missing %q", expect) req, err = http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL()+"/index.yaml-nosuchthing", http.NoBody) require.NoError(t, err) res, err = client.Do(req) require.NoError(t, err) res.Body.Close() - if res.StatusCode != http.StatusNotFound { - t.Fatalf("Expected 404, got %d", res.StatusCode) - } + require.Equal(t, http.StatusNotFound, res.StatusCode) }) } } @@ -199,7 +167,5 @@ func TestNewTempServer_TLS(t *testing.T) { ) defer srv.Stop() - if !strings.HasPrefix(srv.URL(), "https://") { - t.Fatal("non-TLS server") - } + require.True(t, strings.HasPrefix(srv.URL(), "https://"), "non-TLS server") } From 3ce02c585a18b38c5dba1177a54c3a0322c53d3c Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Tue, 16 Jun 2026 16:26:28 +1200 Subject: [PATCH 371/571] refactor(internal): convert tests to testify assert/require Convert internal/fileutil, internal/tlsutil, and internal/resolver test files from native Go testing patterns (t.Errorf, t.Fatalf, t.Error, t.Fatal) to github.com/stretchr/testify equivalents (assert.X, require.X). Signed-off-by: George Jenkins --- internal/fileutil/fileutil_test.go | 70 +++++++++--------------------- internal/resolver/resolver_test.go | 51 +++++++++------------- internal/tlsutil/tls_test.go | 55 +++++++---------------- 3 files changed, 56 insertions(+), 120 deletions(-) diff --git a/internal/fileutil/fileutil_test.go b/internal/fileutil/fileutil_test.go index 71fcae177..07cbe7695 100644 --- a/internal/fileutil/fileutil_test.go +++ b/internal/fileutil/fileutil_test.go @@ -22,6 +22,9 @@ import ( "path/filepath" "strings" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // TestAtomicWriteFile tests the happy path of AtomicWriteFile function. @@ -35,28 +38,17 @@ func TestAtomicWriteFile(t *testing.T) { mode := os.FileMode(0644) err := AtomicWriteFile(testpath, reader, mode) - if err != nil { - t.Errorf("AtomicWriteFile error: %s", err) - } + assert.NoError(t, err) got, err := os.ReadFile(testpath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if stringContent != string(got) { - t.Fatalf("expected: %s, got: %s", stringContent, string(got)) - } + require.Equal(t, stringContent, string(got)) gotinfo, err := os.Stat(testpath) - if err != nil { - t.Fatal(err) - } - - if mode != gotinfo.Mode() { - t.Fatalf("expected %s: to be the same mode as %s", - mode, gotinfo.Mode()) - } + require.NoError(t, err) + + require.Equal(t, mode, gotinfo.Mode()) } // TestAtomicWriteFile_CreateTempError tests the error path when os.CreateTemp fails @@ -67,9 +59,7 @@ func TestAtomicWriteFile_CreateTempError(t *testing.T) { mode := os.FileMode(0644) err := AtomicWriteFile(invalidPath, reader, mode) - if err == nil { - t.Error("Expected error when CreateTemp fails, but got nil") - } + assert.Error(t, err, "Expected error when CreateTemp fails") } // TestAtomicWriteFile_EmptyContent tests with empty content @@ -81,18 +71,12 @@ func TestAtomicWriteFile_EmptyContent(t *testing.T) { mode := os.FileMode(0644) err := AtomicWriteFile(testpath, reader, mode) - if err != nil { - t.Errorf("AtomicWriteFile error with empty content: %s", err) - } + assert.NoError(t, err, "AtomicWriteFile error with empty content") got, err := os.ReadFile(testpath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if len(got) != 0 { - t.Fatalf("expected empty content, got: %s", string(got)) - } + require.Empty(t, got) } // TestAtomicWriteFile_LargeContent tests with large content @@ -106,18 +90,12 @@ func TestAtomicWriteFile_LargeContent(t *testing.T) { mode := os.FileMode(0644) err := AtomicWriteFile(testpath, reader, mode) - if err != nil { - t.Errorf("AtomicWriteFile error with large content: %s", err) - } + assert.NoError(t, err, "AtomicWriteFile error with large content") got, err := os.ReadFile(testpath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if largeContent != string(got) { - t.Fatalf("expected large content to match, got different length: %d vs %d", len(largeContent), len(got)) - } + require.Equal(t, largeContent, string(got)) } // TestPlatformAtomicWriteFile_OverwritesExisting verifies that the platform @@ -127,21 +105,13 @@ func TestPlatformAtomicWriteFile_OverwritesExisting(t *testing.T) { path := filepath.Join(dir, "overwrite_test") first := bytes.NewReader([]byte("first")) - if err := PlatformAtomicWriteFile(path, first, 0644); err != nil { - t.Fatalf("first write failed: %v", err) - } + require.NoError(t, PlatformAtomicWriteFile(path, first, 0644), "first write failed") second := bytes.NewReader([]byte("second")) - if err := PlatformAtomicWriteFile(path, second, 0644); err != nil { - t.Fatalf("second write failed: %v", err) - } + require.NoError(t, PlatformAtomicWriteFile(path, second, 0644), "second write failed") contents, err := os.ReadFile(path) - if err != nil { - t.Fatalf("failed reading result: %v", err) - } + require.NoError(t, err, "failed reading result") - if string(contents) != "second" { - t.Fatalf("expected file to be overwritten, got %q", string(contents)) - } + require.Equal(t, "second", string(contents)) } diff --git a/internal/resolver/resolver_test.go b/internal/resolver/resolver_test.go index 1e33837a9..547419e17 100644 --- a/internal/resolver/resolver_test.go +++ b/internal/resolver/resolver_test.go @@ -19,6 +19,9 @@ import ( "runtime" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/registry" ) @@ -149,34 +152,24 @@ func TestResolve(t *testing.T) { if tt.err { return } - t.Fatal(err) + require.NoError(t, err) } if tt.err { - t.Fatalf("Expected error in test %q", tt.name) + require.Failf(t, "Expected error in test %q", tt.name) } - if h, err := HashReq(tt.req, tt.expect.Dependencies); err != nil { - t.Fatal(err) - } else if h != l.Digest { - t.Errorf("%q: hashes don't match.", tt.name) - } + h, err := HashReq(tt.req, tt.expect.Dependencies) + require.NoError(t, err) + assert.Equal(t, l.Digest, h, "%q: hashes don't match.", tt.name) // Check fields. - if len(l.Dependencies) != len(tt.req) { - t.Errorf("%s: wrong number of dependencies in lock", tt.name) - } + assert.Len(t, l.Dependencies, len(tt.req), "%s: wrong number of dependencies in lock", tt.name) d0 := l.Dependencies[0] e0 := tt.expect.Dependencies[0] - if d0.Name != e0.Name { - t.Errorf("%s: expected name %s, got %s", tt.name, e0.Name, d0.Name) - } - if d0.Repository != e0.Repository { - t.Errorf("%s: expected repo %s, got %s", tt.name, e0.Repository, d0.Repository) - } - if d0.Version != e0.Version { - t.Errorf("%s: expected version %s, got %s", tt.name, e0.Version, d0.Version) - } + assert.Equal(t, e0.Name, d0.Name, tt.name) + assert.Equal(t, e0.Repository, d0.Repository, tt.name) + assert.Equal(t, e0.Version, d0.Version, tt.name) }) } } @@ -231,13 +224,11 @@ func TestHashReq(t *testing.T) { {Name: "alpine", Version: tt.lockVersion, Repository: "http://localhost:8879/charts"}, } h, err := HashReq(req, lock) - if err != nil { - t.Fatal(err) - } - if !tt.wantError && expect != h { - t.Errorf("Expected %q, got %q", expect, h) - } else if tt.wantError && expect == h { - t.Errorf("Expected not %q, but same", expect) + require.NoError(t, err) + if !tt.wantError { + assert.Equal(t, expect, h) + } else { + assert.NotEqual(t, expect, h, "Expected not %q, but same", expect) } }) } @@ -293,18 +284,16 @@ func TestGetLocalPath(t *testing.T) { if tt.err { return } - t.Fatal(err) + require.NoError(t, err) } if tt.err { - t.Fatalf("Expected error in test %q", tt.name) + require.Failf(t, "Expected error in test %q", tt.name) } expect := tt.expect if runtime.GOOS == "windows" { expect = tt.winExpect } - if p != expect { - t.Errorf("%q: expected %q, got %q", tt.name, expect, p) - } + assert.Equal(t, expect, p, tt.name) }) } } diff --git a/internal/tlsutil/tls_test.go b/internal/tlsutil/tls_test.go index e6859f99f..324b00969 100644 --- a/internal/tlsutil/tls_test.go +++ b/internal/tlsutil/tls_test.go @@ -19,6 +19,9 @@ package tlsutil import ( "path/filepath" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const tlsTestDir = "../../testdata" @@ -32,9 +35,7 @@ const ( func testfile(t *testing.T, file string) (path string) { t.Helper() path, err := filepath.Abs(filepath.Join(tlsTestDir, file)) - if err != nil { - t.Fatalf("error getting absolute path to test file %q: %v", file, err) - } + require.NoError(t, err, "error getting absolute path to test file %q", file) return path } @@ -50,38 +51,22 @@ func TestNewTLSConfig(t *testing.T) { WithCertKeyPairFiles(certFile, keyFile), WithCAFile(caCertFile), ) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) - if got := len(cfg.Certificates); got != 1 { - t.Fatalf("expecting 1 client certificates, got %d", got) - } - if cfg.InsecureSkipVerify { - t.Fatal("insecure skip verify mismatch, expecting false") - } - if cfg.RootCAs == nil { - t.Fatal("mismatch tls RootCAs, expecting non-nil") - } + require.Len(t, cfg.Certificates, 1) + require.False(t, cfg.InsecureSkipVerify, "insecure skip verify mismatch, expecting false") + require.NotNil(t, cfg.RootCAs, "mismatch tls RootCAs, expecting non-nil") } { cfg, err := NewTLSConfig( WithInsecureSkipVerify(insecureSkipTLSVerify), WithCAFile(caCertFile), ) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) - if got := len(cfg.Certificates); got != 0 { - t.Fatalf("expecting 0 client certificates, got %d", got) - } - if cfg.InsecureSkipVerify { - t.Fatal("insecure skip verify mismatch, expecting false") - } - if cfg.RootCAs == nil { - t.Fatal("mismatch tls RootCAs, expecting non-nil") - } + require.Empty(t, cfg.Certificates) + require.False(t, cfg.InsecureSkipVerify, "insecure skip verify mismatch, expecting false") + require.NotNil(t, cfg.RootCAs, "mismatch tls RootCAs, expecting non-nil") } { @@ -89,18 +74,10 @@ func TestNewTLSConfig(t *testing.T) { WithInsecureSkipVerify(insecureSkipTLSVerify), WithCertKeyPairFiles(certFile, keyFile), ) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) - if got := len(cfg.Certificates); got != 1 { - t.Fatalf("expecting 1 client certificates, got %d", got) - } - if cfg.InsecureSkipVerify { - t.Fatal("insecure skip verify mismatch, expecting false") - } - if cfg.RootCAs != nil { - t.Fatal("mismatch tls RootCAs, expecting nil") - } + require.Len(t, cfg.Certificates, 1) + require.False(t, cfg.InsecureSkipVerify, "insecure skip verify mismatch, expecting false") + require.Nil(t, cfg.RootCAs, "mismatch tls RootCAs, expecting nil") } } From 8b0a167484a7f6de4a2cb12bf81641873a275d7b Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Wed, 17 Jun 2026 21:36:47 +1200 Subject: [PATCH 372/571] refactor(pkg/registry): convert tests to testify assert/require Replace native Go testing patterns (t.Errorf, t.Fatalf, t.Error, t.Fatal) with github.com/stretchr/testify equivalents (assert.X, require.X) for improved test readability and error messages. Signed-off-by: George Jenkins --- pkg/registry/chart_test.go | 39 +++++++------------ pkg/registry/client_test.go | 31 +++++---------- pkg/registry/plugin_test.go | 17 +++----- pkg/registry/reference_test.go | 66 ++++++++++--------------------- pkg/registry/tag_test.go | 71 +++++++++------------------------- pkg/registry/transport_test.go | 35 ++++++----------- 6 files changed, 79 insertions(+), 180 deletions(-) diff --git a/pkg/registry/chart_test.go b/pkg/registry/chart_test.go index 0b247601b..23a32506d 100644 --- a/pkg/registry/chart_test.go +++ b/pkg/registry/chart_test.go @@ -17,11 +17,12 @@ limitations under the License. package registry // import "helm.sh/helm/v4/pkg/registry" import ( - "reflect" "testing" "time" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" chart "helm.sh/helm/v4/pkg/chart/v2" ) @@ -147,10 +148,7 @@ func TestGenerateOCIChartAnnotations(t *testing.T) { for _, tt := range tests { result := generateChartOCIAnnotations(tt.chart, nowString) - - if !reflect.DeepEqual(tt.expect, result) { - t.Errorf("%s: expected map %v, got %v", tt.name, tt.expect, result) - } + assert.Equal(t, tt.expect, result, tt.name) } } @@ -218,10 +216,7 @@ func TestGenerateOCIAnnotations(t *testing.T) { for _, tt := range tests { result := generateOCIAnnotations(tt.chart, nowString) - - if !reflect.DeepEqual(tt.expect, result) { - t.Errorf("%s: expected map %v, got %v", tt.name, tt.expect, result) - } + assert.Equal(t, tt.expect, result, tt.name) } } @@ -237,29 +232,23 @@ func TestGenerateOCICreatedAnnotations(t *testing.T) { result := generateOCIAnnotations(testChart, nowTimeString) // Check that created annotation exists - if _, ok := result[ocispec.AnnotationCreated]; !ok { - t.Errorf("%s annotation not created", ocispec.AnnotationCreated) - } + _, ok := result[ocispec.AnnotationCreated] + assert.True(t, ok, "%s annotation not created", ocispec.AnnotationCreated) // Verify value of created artifact in RFC3339 format - if _, err := time.Parse(time.RFC3339, result[ocispec.AnnotationCreated]); err != nil { - t.Errorf("%s annotation with value '%s' not in RFC3339 format", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated]) - } + _, err := time.Parse(time.RFC3339, result[ocispec.AnnotationCreated]) + assert.NoError(t, err, "%s annotation with value '%s' not in RFC3339 format", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated]) // Verify default creation time set result = generateOCIAnnotations(testChart, "") // Check that created annotation exists - if _, ok := result[ocispec.AnnotationCreated]; !ok { - t.Errorf("%s annotation not created", ocispec.AnnotationCreated) - } + _, ok = result[ocispec.AnnotationCreated] + require.True(t, ok, "%s annotation not created", ocispec.AnnotationCreated) - if createdTimeAnnotation, err := time.Parse(time.RFC3339, result[ocispec.AnnotationCreated]); err != nil { - t.Errorf("%s annotation with value '%s' not in RFC3339 format", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated]) + createdTimeAnnotation, err := time.Parse(time.RFC3339, result[ocispec.AnnotationCreated]) + require.NoError(t, err, "%s annotation with value '%s' not in RFC3339 format", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated]) - // Verify creation annotation after time test began - if !nowTime.Before(createdTimeAnnotation) { - t.Errorf("%s annotation with value '%s' not configured properly. Annotation value is not after %s", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated], nowTimeString) - } - } + // Verify creation annotation after (or equals) time test began + assert.False(t, nowTime.Before(createdTimeAnnotation), "%s annotation with value '%s' not configured properly. Annotation value is not after %s", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated], nowTimeString) } diff --git a/pkg/registry/client_test.go b/pkg/registry/client_test.go index 702dfff69..97b7c7c4c 100644 --- a/pkg/registry/client_test.go +++ b/pkg/registry/client_test.go @@ -25,6 +25,7 @@ import ( "testing" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "oras.land/oras-go/v2/content/memory" ) @@ -77,22 +78,15 @@ func TestLogin_ResetsForceAttemptOAuth2_OnSuccess(t *testing.T) { ClientOptWriter(io.Discard), ClientOptCredentialsFile(credFile), ) - if err != nil { - t.Fatalf("NewClient error: %v", err) - } + require.NoError(t, err, "NewClient error") - if c.authorizer == nil || c.authorizer.ForceAttemptOAuth2 { - t.Fatal("expected ForceAttemptOAuth2 default to be false") - } + require.NotNil(t, c.authorizer) + require.False(t, c.authorizer.ForceAttemptOAuth2, "expected ForceAttemptOAuth2 default to be false") // Call Login with plain HTTP against our test server - if err := c.Login(host, LoginOptPlainText(true), LoginOptBasicAuth("u", "p")); err != nil { - t.Fatalf("Login error: %v", err) - } + require.NoError(t, c.Login(host, LoginOptPlainText(true), LoginOptBasicAuth("u", "p")), "Login error") - if c.authorizer.ForceAttemptOAuth2 { - t.Error("ForceAttemptOAuth2 should be false after successful Login") - } + assert.False(t, c.authorizer.ForceAttemptOAuth2, "ForceAttemptOAuth2 should be false after successful Login") } // Verifies that Login restores ForceAttemptOAuth2 to false even when ping fails. @@ -109,16 +103,12 @@ func TestLogin_ResetsForceAttemptOAuth2_OnFailure(t *testing.T) { ClientOptWriter(io.Discard), ClientOptCredentialsFile(credFile), ) - if err != nil { - t.Fatalf("NewClient error: %v", err) - } + require.NoError(t, err, "NewClient error") // Invoke Login, expect an error but ForceAttemptOAuth2 must end false _ = c.Login(host, LoginOptPlainText(true), LoginOptBasicAuth("u", "p")) - if c.authorizer.ForceAttemptOAuth2 { - t.Error("ForceAttemptOAuth2 should be false after failed Login") - } + assert.False(t, c.authorizer.ForceAttemptOAuth2, "ForceAttemptOAuth2 should be false after failed Login") } // TestWarnIfHostHasPath verifies that warnIfHostHasPath correctly detects path components. @@ -159,10 +149,7 @@ func TestWarnIfHostHasPath(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := warnIfHostHasPath(tt.host) - if got != tt.wantWarn { - t.Errorf("warnIfHostHasPath(%q) = %v, want %v", tt.host, got, tt.wantWarn) - } + assert.Equal(t, tt.wantWarn, warnIfHostHasPath(tt.host)) }) } } diff --git a/pkg/registry/plugin_test.go b/pkg/registry/plugin_test.go index 61995ece9..bb557c5b8 100644 --- a/pkg/registry/plugin_test.go +++ b/pkg/registry/plugin_test.go @@ -18,6 +18,9 @@ package registry import ( "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestGetPluginName(t *testing.T) { @@ -74,20 +77,12 @@ func TestGetPluginName(t *testing.T) { pluginName, err := GetPluginName(tt.source) if tt.expectErr { - if err == nil { - t.Error("expected error but got none") - } + assert.Error(t, err, "expected error but got none") return } - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - if pluginName != tt.expected { - t.Errorf("expected plugin name %q, got %q", tt.expected, pluginName) - } + require.NoError(t, err) + assert.Equal(t, tt.expected, pluginName) }) } } diff --git a/pkg/registry/reference_test.go b/pkg/registry/reference_test.go index b6872cc37..f1a87fefc 100644 --- a/pkg/registry/reference_test.go +++ b/pkg/registry/reference_test.go @@ -16,31 +16,21 @@ limitations under the License. package registry -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" +) func verify(t *testing.T, actual reference, registry, repository, tag, digest string) { t.Helper() - if registry != actual.orasReference.Registry { - t.Errorf("Oras reference registry expected %v actual %v", registry, actual.Registry) - } - if repository != actual.orasReference.Repository { - t.Errorf("Oras reference repository expected %v actual %v", repository, actual.Repository) - } - if tag != actual.orasReference.Reference { - t.Errorf("Oras reference reference expected %v actual %v", tag, actual.Tag) - } - if registry != actual.Registry { - t.Errorf("Registry expected %v actual %v", registry, actual.Registry) - } - if repository != actual.Repository { - t.Errorf("Repository expected %v actual %v", repository, actual.Repository) - } - if tag != actual.Tag { - t.Errorf("Tag expected %v actual %v", tag, actual.Tag) - } - if digest != actual.Digest { - t.Errorf("Digest expected %v actual %v", digest, actual.Digest) - } + assert.Equal(t, registry, actual.orasReference.Registry, "Oras reference registry") + assert.Equal(t, repository, actual.orasReference.Repository, "Oras reference repository") + assert.Equal(t, tag, actual.orasReference.Reference, "Oras reference reference") + assert.Equal(t, registry, actual.Registry, "Registry") + assert.Equal(t, repository, actual.Repository, "Repository") + assert.Equal(t, tag, actual.Tag, "Tag") + assert.Equal(t, digest, actual.Digest, "Digest") expectedString := registry if repository != "" { expectedString = expectedString + "/" + repository @@ -50,51 +40,35 @@ func verify(t *testing.T, actual reference, registry, repository, tag, digest st } else { expectedString = expectedString + "@" + digest } - if actual.String() != expectedString { - t.Errorf("String expected %s actual %s", expectedString, actual.String()) - } + assert.Equal(t, expectedString, actual.String(), "String") } func TestNewReference(t *testing.T) { actual, err := newReference("registry.example.com/repository:1.0@sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") - if err != nil { - t.Errorf("Unexpected error %v", err) - } + assert.NoError(t, err) verify(t, actual, "registry.example.com", "repository", "1.0", "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") actual, err = newReference("oci://registry.example.com/repository:1.0@sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") - if err != nil { - t.Errorf("Unexpected error %v", err) - } + assert.NoError(t, err) verify(t, actual, "registry.example.com", "repository", "1.0", "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") actual, err = newReference("a/b:1@c") - if err != nil { - t.Errorf("Unexpected error %v", err) - } + assert.NoError(t, err) verify(t, actual, "a", "b", "1", "c") actual, err = newReference("a/b:@") - if err != nil { - t.Errorf("Unexpected error %v", err) - } + assert.NoError(t, err) verify(t, actual, "a", "b", "", "") actual, err = newReference("registry.example.com/repository:1.0+001") - if err != nil { - t.Errorf("Unexpected error %v", err) - } + assert.NoError(t, err) verify(t, actual, "registry.example.com", "repository", "1.0_001", "") actual, err = newReference("thing:1.0") - if err == nil { - t.Errorf("Expect error error %v", err) - } + assert.Error(t, err) verify(t, actual, "", "", "", "") actual, err = newReference("registry.example.com/the/repository@sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") - if err != nil { - t.Errorf("Unexpected error %v", err) - } + assert.NoError(t, err) verify(t, actual, "registry.example.com", "the/repository", "", "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") } diff --git a/pkg/registry/tag_test.go b/pkg/registry/tag_test.go index e3ec47703..830eac7e1 100644 --- a/pkg/registry/tag_test.go +++ b/pkg/registry/tag_test.go @@ -17,32 +17,25 @@ limitations under the License. package registry import ( - "strings" "testing" + + "github.com/stretchr/testify/require" ) func TestGetTagMatchingVersionOrConstraint_ExactMatch(t *testing.T) { tags := []string{"1.0.0", "1.2.3", "2.0.0"} got, err := GetTagMatchingVersionOrConstraint(tags, "1.2.3") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if got != "1.2.3" { - t.Fatalf("expected exact match '1.2.3', got %q", got) - } + require.NoError(t, err) + require.Equal(t, "1.2.3", got, "expected exact match") } func TestGetTagMatchingVersionOrConstraint_EmptyVersionWildcard(t *testing.T) { // Includes a non-semver tag which should be skipped tags := []string{"latest", "0.9.0", "1.0.0"} got, err := GetTagMatchingVersionOrConstraint(tags, "") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } + require.NoError(t, err) // Should pick the first valid semver tag in order, which is 0.9.0 - if got != "0.9.0" { - t.Fatalf("expected '0.9.0', got %q", got) - } + require.Equal(t, "0.9.0", got) } func TestGetTagMatchingVersionOrConstraint_ConstraintRange(t *testing.T) { @@ -50,73 +43,47 @@ func TestGetTagMatchingVersionOrConstraint_ConstraintRange(t *testing.T) { // Caret range got, err := GetTagMatchingVersionOrConstraint(tags, "^1.0.0") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if got != "1.0.0" { // first match in order - t.Fatalf("expected '1.0.0', got %q", got) - } + require.NoError(t, err) + require.Equal(t, "1.0.0", got, "first match in order") // Compound range got, err = GetTagMatchingVersionOrConstraint(tags, ">=1.0.0 <2.0.0") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if got != "1.0.0" { - t.Fatalf("expected '1.0.0', got %q", got) - } + require.NoError(t, err) + require.Equal(t, "1.0.0", got) } func TestGetTagMatchingVersionOrConstraint_InvalidConstraint(t *testing.T) { tags := []string{"1.0.0"} _, err := GetTagMatchingVersionOrConstraint(tags, ">a1") - if err == nil { - t.Fatal("expected error for invalid constraint") - } + require.Error(t, err, "expected error for invalid constraint") } func TestGetTagMatchingVersionOrConstraint_NoMatches(t *testing.T) { tags := []string{"0.1.0", "0.2.0"} _, err := GetTagMatchingVersionOrConstraint(tags, ">=1.0.0") - if err == nil { - t.Fatal("expected error when no tags match") - } - if !strings.Contains(err.Error(), ">=1.0.0") { - t.Fatalf("expected error to contain version string, got: %v", err) - } + require.Error(t, err, "expected error when no tags match") + require.Contains(t, err.Error(), ">=1.0.0", "expected error to contain version string") } func TestGetTagMatchingVersionOrConstraint_SkipsNonSemverTags(t *testing.T) { tags := []string{"alpha", "1.0.0", "beta", "1.1.0"} got, err := GetTagMatchingVersionOrConstraint(tags, ">=1.0.0 <2.0.0") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if got != "1.0.0" { - t.Fatalf("expected '1.0.0', got %q", got) - } + require.NoError(t, err) + require.Equal(t, "1.0.0", got) } func TestGetTagMatchingVersionOrConstraint_OrderMatters_FirstMatchReturned(t *testing.T) { // Both 1.2.0 and 1.3.0 satisfy >=1.2.0 <2.0.0, but the function returns the first in input order tags := []string{"1.3.0", "1.2.0"} got, err := GetTagMatchingVersionOrConstraint(tags, ">=1.2.0 <2.0.0") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if got != "1.3.0" { - t.Fatalf("expected '1.3.0' (first satisfying tag), got %q", got) - } + require.NoError(t, err) + require.Equal(t, "1.3.0", got, "first satisfying tag") } func TestGetTagMatchingVersionOrConstraint_ExactMatchHasPrecedence(t *testing.T) { // Exact match should be returned even if another earlier tag would match the parsed constraint tags := []string{"1.3.0", "1.2.3"} got, err := GetTagMatchingVersionOrConstraint(tags, "1.2.3") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if got != "1.2.3" { - t.Fatalf("expected exact match '1.2.3', got %q", got) - } + require.NoError(t, err) + require.Equal(t, "1.2.3", got, "expected exact match") } diff --git a/pkg/registry/transport_test.go b/pkg/registry/transport_test.go index b4990c526..e72c97c0d 100644 --- a/pkg/registry/transport_test.go +++ b/pkg/registry/transport_test.go @@ -22,6 +22,9 @@ import ( "io" "net/http" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var errMockRead = errors.New("mock read error") @@ -127,9 +130,7 @@ func Test_isPrintableContentType(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := isPrintableContentType(tt.contentType); got != tt.want { - t.Errorf("isPrintableContentType() = %v, want %v", got, tt.want) - } + assert.Equal(t, tt.want, isPrintableContentType(tt.contentType)) }) } } @@ -292,21 +293,13 @@ func Test_logResponseBody(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := logResponseBody(tt.resp); got != tt.want { - t.Errorf("logResponseBody() = %v, want %v", got, tt.want) - } + assert.Equal(t, tt.want, logResponseBody(tt.resp)) // validate the response body if tt.resp.Body != nil { readBytes, err := io.ReadAll(tt.resp.Body) - if err != nil { - t.Errorf("failed to read body after logResponseBody(), err= %v", err) - } - if !bytes.Equal(readBytes, tt.wantData) { - t.Errorf("resp.Body after logResponseBody() = %v, want %v", readBytes, tt.wantData) - } - if closeErr := tt.resp.Body.Close(); closeErr != nil { - t.Errorf("failed to close body after logResponseBody(), err= %v", closeErr) - } + require.NoError(t, err, "failed to read body after logResponseBody()") + assert.True(t, bytes.Equal(tt.wantData, readBytes), "resp.Body after logResponseBody()") + assert.NoError(t, tt.resp.Body.Close(), "failed to close body after logResponseBody()") } }) } @@ -331,12 +324,8 @@ func Test_logResponseBody_error(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := logResponseBody(tt.resp); got != tt.want { - t.Errorf("logResponseBody() = %v, want %v", got, tt.want) - } - if closeErr := tt.resp.Body.Close(); closeErr != nil { - t.Errorf("failed to close body after logResponseBody(), err= %v", closeErr) - } + assert.Equal(t, tt.want, logResponseBody(tt.resp)) + assert.NoError(t, tt.resp.Body.Close(), "failed to close body after logResponseBody()") }) } } @@ -391,9 +380,7 @@ func Test_containsCredentials(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := containsCredentials(tt.body); got != tt.want { - t.Errorf("containsCredentials() = %v, want %v", got, tt.want) - } + assert.Equal(t, tt.want, containsCredentials(tt.body)) }) } } From 4cce0e9e2a870508864c1d497918e201dc0f339c Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Wed, 17 Jun 2026 21:32:52 +1200 Subject: [PATCH 373/571] refactor(pkg/release): convert tests to testify assert/require Replace native Go testing patterns (t.Errorf, t.Fatalf, t.Error, t.Fatal) with github.com/stretchr/testify equivalents (assert.X, require.X) for improved test readability and error messages. Signed-off-by: George Jenkins --- pkg/release/v1/util/filter_test.go | 29 ++++++---------- pkg/release/v1/util/kind_sorter_test.go | 35 ++++++------------- pkg/release/v1/util/manifest_sorter_test.go | 38 ++++++--------------- pkg/release/v1/util/manifest_test.go | 6 ++-- pkg/release/v1/util/sorter_test.go | 6 ++-- 5 files changed, 38 insertions(+), 76 deletions(-) diff --git a/pkg/release/v1/util/filter_test.go b/pkg/release/v1/util/filter_test.go index 1004a4c57..d6bda8f84 100644 --- a/pkg/release/v1/util/filter_test.go +++ b/pkg/release/v1/util/filter_test.go @@ -19,23 +19,19 @@ package util // import "helm.sh/helm/v4/pkg/release/v1/util" import ( "testing" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/release/common" rspb "helm.sh/helm/v4/pkg/release/v1" ) func TestFilterAny(t *testing.T) { ls := Any(StatusFilter(common.StatusUninstalled)).Filter(releases) - if len(ls) != 2 { - t.Fatalf("expected 2 results, got '%d'", len(ls)) - } + require.Len(t, ls, 2) r0, r1 := ls[0], ls[1] - switch { - case r0.Info.Status != common.StatusUninstalled: - t.Fatalf("expected UNINSTALLED result, got '%s'", r1.Info.Status.String()) - case r1.Info.Status != common.StatusUninstalled: - t.Fatalf("expected UNINSTALLED result, got '%s'", r1.Info.Status.String()) - } + require.Equal(t, common.StatusUninstalled, r0.Info.Status) + require.Equal(t, common.StatusUninstalled, r1.Info.Status) } func TestFilterAll(t *testing.T) { @@ -47,14 +43,9 @@ func TestFilterAll(t *testing.T) { }) ls := All(fn).Filter(releases) - if len(ls) != 1 { - t.Fatalf("expected 1 result, got '%d'", len(ls)) - } - - switch r0 := ls[0]; { - case r0.Version == 4: - t.Fatal("got release with status revision 4") - case r0.Info.Status == common.StatusUninstalled: - t.Fatal("got release with status UNINSTALLED") - } + require.Len(t, ls, 1) + + r0 := ls[0] + require.NotEqual(t, 4, r0.Version, "got release with status revision 4") + require.NotEqual(t, common.StatusUninstalled, r0.Info.Status, "got release with status UNINSTALLED") } diff --git a/pkg/release/v1/util/kind_sorter_test.go b/pkg/release/v1/util/kind_sorter_test.go index 919de24e5..793ef87fe 100644 --- a/pkg/release/v1/util/kind_sorter_test.go +++ b/pkg/release/v1/util/kind_sorter_test.go @@ -20,6 +20,9 @@ import ( "bytes" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + release "helm.sh/helm/v4/pkg/release/v1" ) @@ -193,21 +196,15 @@ func TestKindSorter(t *testing.T) { } { var buf bytes.Buffer t.Run(test.description, func(t *testing.T) { - if got, want := len(test.expected), len(manifests); got != want { - t.Fatalf("Expected %d names in order, got %d", want, got) - } + require.Equal(t, len(manifests), len(test.expected), "Expected %d names in order", len(manifests)) defer buf.Reset() orig := manifests for _, r := range sortManifestsByKind(manifests, test.order) { buf.WriteString(r.Name) } - if got := buf.String(); got != test.expected { - t.Errorf("Expected %q, got %q", test.expected, got) - } + assert.Equal(t, test.expected, buf.String()) for i, manifest := range orig { - if manifest != manifests[i] { - t.Fatal("Expected input to sortManifestsByKind to stay the same") - } + require.Equal(t, manifest, manifests[i], "Expected input to sortManifestsByKind to stay the same") } }) } @@ -267,9 +264,7 @@ func TestKindSorterKeepOriginalOrder(t *testing.T) { for _, r := range sortManifestsByKind(manifests, test.order) { buf.WriteString(r.Name) } - if got := buf.String(); got != test.expected { - t.Errorf("Expected %q, got %q", test.expected, got) - } + assert.Equal(t, test.expected, buf.String()) }) } } @@ -289,9 +284,7 @@ func TestKindSorterNamespaceAgainstUnknown(t *testing.T) { expectedOrder := []Manifest{namespace, unknown} for i, manifest := range manifests { - if expectedOrder[i].Name != manifest.Name { - t.Errorf("Expected %s, got %s", expectedOrder[i].Name, manifest.Name) - } + assert.Equal(t, expectedOrder[i].Name, manifest.Name) } } @@ -326,22 +319,16 @@ func TestKindSorterForHooks(t *testing.T) { } { var buf bytes.Buffer t.Run(test.description, func(t *testing.T) { - if got, want := len(test.expected), len(hooks); got != want { - t.Fatalf("Expected %d names in order, got %d", want, got) - } + require.Equal(t, len(hooks), len(test.expected), "Expected %d names in order", len(hooks)) defer buf.Reset() orig := hooks for _, r := range sortHooksByKind(hooks, test.order) { buf.WriteString(r.Name) } for i, hook := range orig { - if hook != hooks[i] { - t.Fatal("Expected input to sortHooksByKind to stay the same") - } - } - if got := buf.String(); got != test.expected { - t.Errorf("Expected %q, got %q", test.expected, got) + require.Equal(t, hook, hooks[i], "Expected input to sortHooksByKind to stay the same") } + assert.Equal(t, test.expected, buf.String()) }) } } diff --git a/pkg/release/v1/util/manifest_sorter_test.go b/pkg/release/v1/util/manifest_sorter_test.go index baa320be0..2cdb55dc3 100644 --- a/pkg/release/v1/util/manifest_sorter_test.go +++ b/pkg/release/v1/util/manifest_sorter_test.go @@ -20,6 +20,8 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "sigs.k8s.io/yaml" release "helm.sh/helm/v4/pkg/release/v1" @@ -138,55 +140,39 @@ metadata: } hs, generic, err := SortManifests(manifests, nil, InstallOrder) - if err != nil { - t.Fatalf("Unexpected error: %s", err) - } + require.NoError(t, err) // This test will fail if 'six' or 'seven' was added. - if len(generic) != 2 { - t.Errorf("Expected 2 generic manifests, got %d", len(generic)) - } + assert.Len(t, generic, 2) - if len(hs) != 4 { - t.Errorf("Expected 4 hooks, got %d", len(hs)) - } + assert.Len(t, hs, 4) for _, out := range hs { found := false for _, expect := range data { if out.Path == expect.path { found = true - if out.Path != expect.path { - t.Errorf("Expected path %s, got %s", expect.path, out.Path) - } + assert.Equal(t, expect.path, out.Path) nameFound := false for _, expectedName := range expect.name { if out.Name == expectedName { nameFound = true } } - if !nameFound { - t.Errorf("Got unexpected name %s", out.Name) - } + assert.True(t, nameFound, "Got unexpected name %s", out.Name) kindFound := false for _, expectedKind := range expect.kind { if out.Kind == expectedKind { kindFound = true } } - if !kindFound { - t.Errorf("Got unexpected kind %s", out.Kind) - } + assert.True(t, kindFound, "Got unexpected kind %s", out.Kind) expectedHooks := expect.hooks[out.Name] - if !reflect.DeepEqual(expectedHooks, out.Events) { - t.Errorf("expected events: %v but got: %v", expectedHooks, out.Events) - } + assert.True(t, reflect.DeepEqual(expectedHooks, out.Events), "expected events: %v but got: %v", expectedHooks, out.Events) } } - if !found { - t.Errorf("Result not found: %v", out) - } + assert.True(t, found, "Result not found: %v", out) } // Verify the sort order @@ -218,8 +204,6 @@ metadata: sorted = sortManifestsByKind(sorted, InstallOrder) for i, m := range generic { - if m.Content != sorted[i].Content { - t.Errorf("Expected %q, got %q", m.Content, sorted[i].Content) - } + assert.Equal(t, sorted[i].Content, m.Content) } } diff --git a/pkg/release/v1/util/manifest_test.go b/pkg/release/v1/util/manifest_test.go index 516ac42d7..6bcf902d7 100644 --- a/pkg/release/v1/util/manifest_test.go +++ b/pkg/release/v1/util/manifest_test.go @@ -19,6 +19,8 @@ package util // import "helm.sh/helm/v4/pkg/release/v1/util" import ( "reflect" "testing" + + "github.com/stretchr/testify/assert" ) func TestSplitManifests(t *testing.T) { @@ -509,9 +511,7 @@ metadata: for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := SplitManifests(tt.input) - if !reflect.DeepEqual(result, tt.expected) { - t.Errorf("SplitManifests() =\n%v\nwant:\n%v", result, tt.expected) - } + assert.True(t, reflect.DeepEqual(result, tt.expected), "SplitManifests() =\n%v\nwant:\n%v", result, tt.expected) }) } } diff --git a/pkg/release/v1/util/sorter_test.go b/pkg/release/v1/util/sorter_test.go index f47db7db8..0450b09e6 100644 --- a/pkg/release/v1/util/sorter_test.go +++ b/pkg/release/v1/util/sorter_test.go @@ -20,6 +20,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "helm.sh/helm/v4/pkg/release/common" rspb "helm.sh/helm/v4/pkg/release/v1" ) @@ -45,9 +47,7 @@ func tsRelease(name string, vers int, dur time.Duration, status common.Status) * func check(t *testing.T, by string, fn func(int, int) bool) { t.Helper() for i := len(releases) - 1; i > 0; i-- { - if fn(i, i-1) { - t.Errorf("release at positions '(%d,%d)' not sorted by %s", i-1, i, by) - } + assert.False(t, fn(i, i-1), "release at positions '(%d,%d)' not sorted by %s", i-1, i, by) } } From 7ab295fcfcf3977599939d76ffbf7a266401bfbc Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Wed, 17 Jun 2026 21:48:26 +1200 Subject: [PATCH 374/571] refactor(internal/release): convert tests to testify assert/require Replace native Go testing patterns (t.Errorf, t.Fatalf, t.Error, t.Fatal) with github.com/stretchr/testify equivalents (assert.X, require.X) for improved test readability and error messages. Signed-off-by: George Jenkins --- internal/release/v2/util/filter_test.go | 29 +++++--------- internal/release/v2/util/kind_sorter_test.go | 35 ++++++----------- .../release/v2/util/manifest_sorter_test.go | 39 ++++++------------- internal/release/v2/util/manifest_test.go | 7 ++-- internal/release/v2/util/sorter_test.go | 6 +-- pkg/release/v1/util/kind_sorter_test.go | 4 +- pkg/release/v1/util/manifest_sorter_test.go | 5 +-- pkg/release/v1/util/manifest_test.go | 3 +- 8 files changed, 43 insertions(+), 85 deletions(-) diff --git a/internal/release/v2/util/filter_test.go b/internal/release/v2/util/filter_test.go index 35236498a..8c7363b87 100644 --- a/internal/release/v2/util/filter_test.go +++ b/internal/release/v2/util/filter_test.go @@ -19,23 +19,19 @@ package util // import "helm.sh/helm/v4/internal/release/v2/util" import ( "testing" + "github.com/stretchr/testify/require" + rspb "helm.sh/helm/v4/internal/release/v2" "helm.sh/helm/v4/pkg/release/common" ) func TestFilterAny(t *testing.T) { ls := Any(StatusFilter(common.StatusUninstalled)).Filter(releases) - if len(ls) != 2 { - t.Fatalf("expected 2 results, got '%d'", len(ls)) - } + require.Len(t, ls, 2) r0, r1 := ls[0], ls[1] - switch { - case r0.Info.Status != common.StatusUninstalled: - t.Fatalf("expected UNINSTALLED result, got '%s'", r1.Info.Status.String()) - case r1.Info.Status != common.StatusUninstalled: - t.Fatalf("expected UNINSTALLED result, got '%s'", r1.Info.Status.String()) - } + require.Equal(t, common.StatusUninstalled, r0.Info.Status) + require.Equal(t, common.StatusUninstalled, r1.Info.Status) } func TestFilterAll(t *testing.T) { @@ -47,14 +43,9 @@ func TestFilterAll(t *testing.T) { }) ls := All(fn).Filter(releases) - if len(ls) != 1 { - t.Fatalf("expected 1 result, got '%d'", len(ls)) - } - - switch r0 := ls[0]; { - case r0.Version == 4: - t.Fatal("got release with status revision 4") - case r0.Info.Status == common.StatusUninstalled: - t.Fatal("got release with status UNINSTALLED") - } + require.Len(t, ls, 1) + + r0 := ls[0] + require.NotEqual(t, 4, r0.Version, "got release with status revision 4") + require.NotEqual(t, common.StatusUninstalled, r0.Info.Status, "got release with status UNINSTALLED") } diff --git a/internal/release/v2/util/kind_sorter_test.go b/internal/release/v2/util/kind_sorter_test.go index 06418a5fc..dba19e287 100644 --- a/internal/release/v2/util/kind_sorter_test.go +++ b/internal/release/v2/util/kind_sorter_test.go @@ -20,6 +20,9 @@ import ( "bytes" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + release "helm.sh/helm/v4/internal/release/v2" ) @@ -193,21 +196,15 @@ func TestKindSorter(t *testing.T) { } { var buf bytes.Buffer t.Run(test.description, func(t *testing.T) { - if got, want := len(test.expected), len(manifests); got != want { - t.Fatalf("Expected %d names in order, got %d", want, got) - } + require.Len(t, manifests, len(test.expected), "Expected %d names in order", len(manifests)) defer buf.Reset() orig := manifests for _, r := range sortManifestsByKind(manifests, test.order) { buf.WriteString(r.Name) } - if got := buf.String(); got != test.expected { - t.Errorf("Expected %q, got %q", test.expected, got) - } + assert.Equal(t, test.expected, buf.String()) for i, manifest := range orig { - if manifest != manifests[i] { - t.Fatal("Expected input to sortManifestsByKind to stay the same") - } + require.Equal(t, manifest, manifests[i], "Expected input to sortManifestsByKind to stay the same") } }) } @@ -267,9 +264,7 @@ func TestKindSorterKeepOriginalOrder(t *testing.T) { for _, r := range sortManifestsByKind(manifests, test.order) { buf.WriteString(r.Name) } - if got := buf.String(); got != test.expected { - t.Errorf("Expected %q, got %q", test.expected, got) - } + assert.Equal(t, test.expected, buf.String()) }) } } @@ -289,9 +284,7 @@ func TestKindSorterNamespaceAgainstUnknown(t *testing.T) { expectedOrder := []Manifest{namespace, unknown} for i, manifest := range manifests { - if expectedOrder[i].Name != manifest.Name { - t.Errorf("Expected %s, got %s", expectedOrder[i].Name, manifest.Name) - } + assert.Equal(t, expectedOrder[i].Name, manifest.Name) } } @@ -326,22 +319,16 @@ func TestKindSorterForHooks(t *testing.T) { } { var buf bytes.Buffer t.Run(test.description, func(t *testing.T) { - if got, want := len(test.expected), len(hooks); got != want { - t.Fatalf("Expected %d names in order, got %d", want, got) - } + require.Len(t, hooks, len(test.expected), "Expected %d names in order", len(hooks)) defer buf.Reset() orig := hooks for _, r := range sortHooksByKind(hooks, test.order) { buf.WriteString(r.Name) } for i, hook := range orig { - if hook != hooks[i] { - t.Fatal("Expected input to sortHooksByKind to stay the same") - } - } - if got := buf.String(); got != test.expected { - t.Errorf("Expected %q, got %q", test.expected, got) + require.Equal(t, hook, hooks[i], "Expected input to sortHooksByKind to stay the same") } + assert.Equal(t, test.expected, buf.String()) }) } } diff --git a/internal/release/v2/util/manifest_sorter_test.go b/internal/release/v2/util/manifest_sorter_test.go index c8851d678..1c932d70c 100644 --- a/internal/release/v2/util/manifest_sorter_test.go +++ b/internal/release/v2/util/manifest_sorter_test.go @@ -17,9 +17,10 @@ limitations under the License. package util // import "helm.sh/helm/v4/internal/release/v2/util" import ( - "reflect" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "sigs.k8s.io/yaml" release "helm.sh/helm/v4/internal/release/v2" @@ -138,55 +139,39 @@ metadata: } hs, generic, err := SortManifests(manifests, nil, InstallOrder) - if err != nil { - t.Fatalf("Unexpected error: %s", err) - } + require.NoError(t, err) // This test will fail if 'six' or 'seven' was added. - if len(generic) != 2 { - t.Errorf("Expected 2 generic manifests, got %d", len(generic)) - } + assert.Len(t, generic, 2) - if len(hs) != 4 { - t.Errorf("Expected 4 hooks, got %d", len(hs)) - } + assert.Len(t, hs, 4) for _, out := range hs { found := false for _, expect := range data { if out.Path == expect.path { found = true - if out.Path != expect.path { - t.Errorf("Expected path %s, got %s", expect.path, out.Path) - } + assert.Equal(t, expect.path, out.Path) nameFound := false for _, expectedName := range expect.name { if out.Name == expectedName { nameFound = true } } - if !nameFound { - t.Errorf("Got unexpected name %s", out.Name) - } + assert.True(t, nameFound, "Got unexpected name %s", out.Name) kindFound := false for _, expectedKind := range expect.kind { if out.Kind == expectedKind { kindFound = true } } - if !kindFound { - t.Errorf("Got unexpected kind %s", out.Kind) - } + assert.True(t, kindFound, "Got unexpected kind %s", out.Kind) expectedHooks := expect.hooks[out.Name] - if !reflect.DeepEqual(expectedHooks, out.Events) { - t.Errorf("expected events: %v but got: %v", expectedHooks, out.Events) - } + assert.Equal(t, expectedHooks, out.Events, "expected events: %v but got: %v", expectedHooks, out.Events) } } - if !found { - t.Errorf("Result not found: %v", out) - } + assert.True(t, found, "Result not found: %v", out) } // Verify the sort order @@ -218,8 +203,6 @@ metadata: sorted = sortManifestsByKind(sorted, InstallOrder) for i, m := range generic { - if m.Content != sorted[i].Content { - t.Errorf("Expected %q, got %q", m.Content, sorted[i].Content) - } + assert.Equal(t, m.Content, sorted[i].Content) } } diff --git a/internal/release/v2/util/manifest_test.go b/internal/release/v2/util/manifest_test.go index 72b095390..463ab5a4d 100644 --- a/internal/release/v2/util/manifest_test.go +++ b/internal/release/v2/util/manifest_test.go @@ -17,8 +17,9 @@ limitations under the License. package util // import "helm.sh/helm/v4/internal/release/v2/util" import ( - "reflect" "testing" + + "github.com/stretchr/testify/assert" ) func TestSplitManifests(t *testing.T) { @@ -509,9 +510,7 @@ metadata: for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := SplitManifests(tt.input) - if !reflect.DeepEqual(result, tt.expected) { - t.Errorf("SplitManifests() =\n%v\nwant:\n%v", result, tt.expected) - } + assert.Equal(t, tt.expected, result, "SplitManifests() =\n%v\nwant:\n%v", result, tt.expected) }) } } diff --git a/internal/release/v2/util/sorter_test.go b/internal/release/v2/util/sorter_test.go index 6cb876f69..82a53ae66 100644 --- a/internal/release/v2/util/sorter_test.go +++ b/internal/release/v2/util/sorter_test.go @@ -20,6 +20,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + rspb "helm.sh/helm/v4/internal/release/v2" "helm.sh/helm/v4/pkg/release/common" ) @@ -45,9 +47,7 @@ func tsRelease(name string, vers int, dur time.Duration, status common.Status) * func check(t *testing.T, by string, fn func(int, int) bool) { t.Helper() for i := len(releases) - 1; i > 0; i-- { - if fn(i, i-1) { - t.Errorf("release at positions '(%d,%d)' not sorted by %s", i-1, i, by) - } + assert.False(t, fn(i, i-1), "release at positions '(%d,%d)' not sorted by %s", i-1, i, by) } } diff --git a/pkg/release/v1/util/kind_sorter_test.go b/pkg/release/v1/util/kind_sorter_test.go index 793ef87fe..de8a79b25 100644 --- a/pkg/release/v1/util/kind_sorter_test.go +++ b/pkg/release/v1/util/kind_sorter_test.go @@ -196,7 +196,7 @@ func TestKindSorter(t *testing.T) { } { var buf bytes.Buffer t.Run(test.description, func(t *testing.T) { - require.Equal(t, len(manifests), len(test.expected), "Expected %d names in order", len(manifests)) + require.Len(t, manifests, len(test.expected), "Expected %d names in order", len(manifests)) defer buf.Reset() orig := manifests for _, r := range sortManifestsByKind(manifests, test.order) { @@ -319,7 +319,7 @@ func TestKindSorterForHooks(t *testing.T) { } { var buf bytes.Buffer t.Run(test.description, func(t *testing.T) { - require.Equal(t, len(hooks), len(test.expected), "Expected %d names in order", len(hooks)) + require.Len(t, hooks, len(test.expected), "Expected %d names in order", len(hooks)) defer buf.Reset() orig := hooks for _, r := range sortHooksByKind(hooks, test.order) { diff --git a/pkg/release/v1/util/manifest_sorter_test.go b/pkg/release/v1/util/manifest_sorter_test.go index 2cdb55dc3..b02400938 100644 --- a/pkg/release/v1/util/manifest_sorter_test.go +++ b/pkg/release/v1/util/manifest_sorter_test.go @@ -17,7 +17,6 @@ limitations under the License. package util import ( - "reflect" "testing" "github.com/stretchr/testify/assert" @@ -169,7 +168,7 @@ metadata: assert.True(t, kindFound, "Got unexpected kind %s", out.Kind) expectedHooks := expect.hooks[out.Name] - assert.True(t, reflect.DeepEqual(expectedHooks, out.Events), "expected events: %v but got: %v", expectedHooks, out.Events) + assert.Equal(t, expectedHooks, out.Events, "expected events: %v but got: %v", expectedHooks, out.Events) } } assert.True(t, found, "Result not found: %v", out) @@ -204,6 +203,6 @@ metadata: sorted = sortManifestsByKind(sorted, InstallOrder) for i, m := range generic { - assert.Equal(t, sorted[i].Content, m.Content) + assert.Equal(t, m.Content, sorted[i].Content) } } diff --git a/pkg/release/v1/util/manifest_test.go b/pkg/release/v1/util/manifest_test.go index 6bcf902d7..d1efa21e1 100644 --- a/pkg/release/v1/util/manifest_test.go +++ b/pkg/release/v1/util/manifest_test.go @@ -17,7 +17,6 @@ limitations under the License. package util // import "helm.sh/helm/v4/pkg/release/v1/util" import ( - "reflect" "testing" "github.com/stretchr/testify/assert" @@ -511,7 +510,7 @@ metadata: for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := SplitManifests(tt.input) - assert.True(t, reflect.DeepEqual(result, tt.expected), "SplitManifests() =\n%v\nwant:\n%v", result, tt.expected) + assert.Equal(t, tt.expected, result, "SplitManifests() =\n%v\nwant:\n%v", result, tt.expected) }) } } From addbab2e6444954eb1a9bde86730ee7d11810b46 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Tue, 16 Jun 2026 15:49:51 +1200 Subject: [PATCH 375/571] refactor(internal): convert tests to testify assert/require Convert internal/urlutil, internal/cli/output, internal/monocular, and internal/sympath test files from native Go testing patterns (t.Errorf, t.Fatalf, t.Error, t.Fatal) to github.com/stretchr/testify equivalents (assert.X, require.X). Signed-off-by: George Jenkins --- internal/cli/output/color_test.go | 18 +++++++---------- internal/monocular/client_test.go | 11 +++++------ internal/monocular/search_test.go | 15 ++++++--------- internal/sympath/walk_test.go | 32 +++++++++++-------------------- internal/urlutil/urlutil_test.go | 23 +++++++++++----------- 5 files changed, 40 insertions(+), 59 deletions(-) diff --git a/internal/cli/output/color_test.go b/internal/cli/output/color_test.go index f0484fc3a..807086c62 100644 --- a/internal/cli/output/color_test.go +++ b/internal/cli/output/color_test.go @@ -20,6 +20,8 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "helm.sh/helm/v4/pkg/release/common" ) @@ -93,14 +95,12 @@ func TestColorizeStatus(t *testing.T) { // In test environment, term.IsTerminal will be false, so we won't get color // unless we're testing the logic without terminal detection - if hasColor && !tt.wantColor { - t.Errorf("ColorizeStatus() returned color when none expected: %q", result) + if hasColor { + assert.True(t, tt.wantColor, "ColorizeStatus() returned color when none expected: %q", result) } // Always check the status text is present - if !strings.Contains(result, tt.status.String()) { - t.Errorf("ColorizeStatus() = %q, want to contain %q", result, tt.status.String()) - } + assert.Contains(t, result, tt.status.String()) }) } } @@ -139,9 +139,7 @@ func TestColorizeHeader(t *testing.T) { result := ColorizeHeader(tt.header, tt.noColor) // Always check the header text is present - if !strings.Contains(result, tt.header) { - t.Errorf("ColorizeHeader() = %q, want to contain %q", result, tt.header) - } + assert.Contains(t, result, tt.header) }) } } @@ -180,9 +178,7 @@ func TestColorizeNamespace(t *testing.T) { result := ColorizeNamespace(tt.namespace, tt.noColor) // Always check the namespace text is present - if !strings.Contains(result, tt.namespace) { - t.Errorf("ColorizeNamespace() = %q, want to contain %q", result, tt.namespace) - } + assert.Contains(t, result, tt.namespace) }) } } diff --git a/internal/monocular/client_test.go b/internal/monocular/client_test.go index abf914ef5..6456557d8 100644 --- a/internal/monocular/client_test.go +++ b/internal/monocular/client_test.go @@ -18,14 +18,13 @@ package monocular import ( "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNew(t *testing.T) { c, err := New("https://hub.helm.sh") - if err != nil { - t.Errorf("error creating client: %s", err) - } - if c.BaseURL != "https://hub.helm.sh" { - t.Errorf("incorrect BaseURL. Expected \"https://hub.helm.sh\" but got %q", c.BaseURL) - } + require.NoError(t, err) + assert.Equal(t, "https://hub.helm.sh", c.BaseURL) } diff --git a/internal/monocular/search_test.go b/internal/monocular/search_test.go index e4475f24e..85327cba7 100644 --- a/internal/monocular/search_test.go +++ b/internal/monocular/search_test.go @@ -21,6 +21,9 @@ import ( "net/http" "net/http/httptest" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // A search response for phpmyadmin containing 2 results @@ -33,16 +36,10 @@ func TestSearch(t *testing.T) { defer ts.Close() c, err := New(ts.URL) - if err != nil { - t.Errorf("unable to create monocular client: %s", err) - } + require.NoError(t, err, "unable to create monocular client") results, err := c.SearchWithContext(t.Context(), "phpmyadmin") - if err != nil { - t.Errorf("unable to search monocular: %s", err) - } + require.NoError(t, err, "unable to search monocular") - if len(results) != 2 { - t.Error("Did not receive the expected number of results") - } + assert.Len(t, results, 2) } diff --git a/internal/sympath/walk_test.go b/internal/sympath/walk_test.go index 1eba8b996..b5bffbdab 100644 --- a/internal/sympath/walk_test.go +++ b/internal/sympath/walk_test.go @@ -24,6 +24,9 @@ import ( "os" "path/filepath" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type Node struct { @@ -80,21 +83,14 @@ func makeTree(t *testing.T) { walkTree(tree, tree.name, func(path string, n *Node) { if n.entries == nil { if n.symLinkedTo != "" { - if err := os.Symlink(n.symLinkedTo, path); err != nil { - t.Fatalf("makeTree: %v", err) - } + require.NoError(t, os.Symlink(n.symLinkedTo, path), "makeTree") } else { fd, err := os.Create(path) - if err != nil { - t.Fatalf("makeTree: %v", err) - return - } + require.NoError(t, err, "makeTree") fd.Close() } } else { - if err := os.Mkdir(path, 0770); err != nil { - t.Fatalf("makeTree: %v", err) - } + require.NoError(t, os.Mkdir(path, 0770), "makeTree") } }) } @@ -102,8 +98,8 @@ func makeTree(t *testing.T) { func checkMarks(t *testing.T, report bool) { t.Helper() walkTree(tree, tree.name, func(path string, n *Node) { - if n.marks != n.expectedMarks && report { - t.Errorf("node %s mark = %d; expected %d", path, n.marks, n.expectedMarks) + if report { + assert.Equal(t, n.expectedMarks, n.marks, "node %s", path) } n.marks = 0 }) @@ -137,16 +133,10 @@ func TestWalk(t *testing.T) { } // Expect no errors. err := Walk(tree.name, markFn) - if err != nil { - t.Fatalf("no error expected, found: %s", err) - } - if len(errors) != 0 { - t.Fatalf("unexpected errors: %s", errors) - } + require.NoError(t, err) + require.Empty(t, errors, "unexpected errors") checkMarks(t, true) // cleanup - if err := os.RemoveAll(tree.name); err != nil { - t.Errorf("removeTree: %v", err) - } + assert.NoError(t, os.RemoveAll(tree.name), "removeTree") } diff --git a/internal/urlutil/urlutil_test.go b/internal/urlutil/urlutil_test.go index 82acc40fe..d6b3f5256 100644 --- a/internal/urlutil/urlutil_test.go +++ b/internal/urlutil/urlutil_test.go @@ -16,7 +16,11 @@ limitations under the License. package urlutil -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" +) func TestURLJoin(t *testing.T) { tests := []struct { @@ -31,11 +35,9 @@ func TestURLJoin(t *testing.T) { } for _, tt := range tests { - if got, err := URLJoin(tt.url, tt.paths...); err != nil { - t.Errorf("%s: error %q", tt.name, err) - } else if got != tt.expect { - t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got) - } + got, err := URLJoin(tt.url, tt.paths...) + assert.NoError(t, err, tt.name) + assert.Equal(t, tt.expect, got, tt.name) } } @@ -60,9 +62,7 @@ func TestEqual(t *testing.T) { {"%/1234", "%/123", false}, {"/1234", "%/1234", false}, } { - if tt.match != Equal(tt.a, tt.b) { - t.Errorf("Expected %q==%q to be %t", tt.a, tt.b, tt.match) - } + assert.Equal(t, tt.match, Equal(tt.a, tt.b), "Expected %q==%q to be %t", tt.a, tt.b, tt.match) } } @@ -74,8 +74,7 @@ func TestExtractHostname(t *testing.T) { "https://example.com:31337/not/with/a/bang/but/a/whimper": "example.com", } for start, expect := range tests { - if got, _ := ExtractHostname(start); got != expect { - t.Errorf("Got %q, expected %q", got, expect) - } + got, _ := ExtractHostname(start) + assert.Equal(t, expect, got) } } From d887779b9b0eeb720672a7927479fc627e599c24 Mon Sep 17 00:00:00 2001 From: Will Noble Date: Fri, 27 Feb 2026 07:43:48 -0800 Subject: [PATCH 376/571] Properly format the extra field in gzipped packages Signed-off-by: Will Noble --- pkg/chart/v2/util/save.go | 2 +- pkg/chart/v2/util/save_test.go | 49 ++++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/pkg/chart/v2/util/save.go b/pkg/chart/v2/util/save.go index e66d86991..09f57da32 100644 --- a/pkg/chart/v2/util/save.go +++ b/pkg/chart/v2/util/save.go @@ -33,7 +33,7 @@ import ( chart "helm.sh/helm/v4/pkg/chart/v2" ) -var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") +var headerBytes = []byte("rr(\x00aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") // SaveDir saves a chart as files in a directory. // diff --git a/pkg/chart/v2/util/save_test.go b/pkg/chart/v2/util/save_test.go index 2f2b73efd..aa562725e 100644 --- a/pkg/chart/v2/util/save_test.go +++ b/pkg/chart/v2/util/save_test.go @@ -130,6 +130,51 @@ func TestSave(t *testing.T) { } } +func TestSavedGzipExtraFieldIsValid(t *testing.T) { + tmp := t.TempDir() + c := &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: chart.APIVersionV2, + Name: "ahab", + Version: "1.2.3", + }, + } + + where, err := Save(c, tmp) + if err != nil { + t.Fatalf("Failed to save: %s", err) + } + + f, err := os.Open(where) + if err != nil { + t.Fatalf("Failed to open saved file: %s", err) + } + defer f.Close() + + r, err := gzip.NewReader(f) + if err != nil { + t.Fatalf("Failed to create gzip reader: %s", err) + } + defer r.Close() + + // RFC 1952 §2.3.1.1: + // Each subfield consists of SI1, SI2 (1 byte each), + // a 2-byte little-endian LEN, and LEN bytes of data. + // https://www.rfc-editor.org/rfc/rfc1952.html#page-8 + // https://github.com/helm/helm/issues/31844 + extra := r.Extra + if len(extra) > 0 { + if len(extra) < 4 { + t.Fatalf("gzip extra field too short to contain a valid subfield: %d byte(s)", len(extra)) + } + dataLen := int(extra[2]) | int(extra[3])<<8 + // Assume a single subfield + if len(extra) != 4+dataLen { + t.Errorf("gzip extra field has malformed subfield: LEN=%d but %d data byte(s) follow the subfield header", dataLen, len(extra)-4) + } + } +} + // Creates a copy with a different schema; does not modify anything. func withSchema(chart chart.Chart, schema []byte) chart.Chart { chart.Schema = schema @@ -299,7 +344,7 @@ func TestRepeatableSave(t *testing.T) { Schema: []byte("{\n \"title\": \"Values\"\n}"), SchemaModTime: modTime, }, - want: "fea2662522317b65c2788ff9e5fc446a9264830038dac618d4449493d99b3257", + want: "63358874b93ea095c857cd66bcf5d0a4464840cf84a07547db744d81d6c5af59", }, { name: "Package 2 files", @@ -321,7 +366,7 @@ func TestRepeatableSave(t *testing.T) { Schema: []byte("{\n \"title\": \"Values\"\n}"), SchemaModTime: modTime, }, - want: "7ae92b2f274bb51ea3f1969e4187d78cc52b5f6f663b44b8fb3b40bcb8ee46f3", + want: "c2a43990053da788ad4e260d3b00d52a0b103ccc67ab9f48278a7b6dcfb2a4bd", }, } for _, test := range tests { From 61bba7c659e3b45ba4db195d58acc668a9c32441 Mon Sep 17 00:00:00 2001 From: Will Noble <4642422+ouillie@users.noreply.github.com> Date: Tue, 23 Jun 2026 00:43:51 +0200 Subject: [PATCH 377/571] Apply suggestions Co-authored-by: George Jenkins Signed-off-by: Will Noble <4642422+ouillie@users.noreply.github.com> --- pkg/chart/v2/util/save.go | 6 +++++- pkg/chart/v2/util/save_test.go | 22 +++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/pkg/chart/v2/util/save.go b/pkg/chart/v2/util/save.go index 09f57da32..19adb8d5f 100644 --- a/pkg/chart/v2/util/save.go +++ b/pkg/chart/v2/util/save.go @@ -33,7 +33,11 @@ import ( chart "helm.sh/helm/v4/pkg/chart/v2" ) -var headerBytes = []byte("rr(\x00aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") +// RFC 1952 subfield header: +// +---+---+---+---+==================================+ +// |SI1|SI2| LEN |... LEN bytes of subfield data ...| +// +---+---+---+---+==================================+ +var headerBytes = []byte("rr\x28\x00aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") // SaveDir saves a chart as files in a directory. // diff --git a/pkg/chart/v2/util/save_test.go b/pkg/chart/v2/util/save_test.go index aa562725e..d23f1e8ac 100644 --- a/pkg/chart/v2/util/save_test.go +++ b/pkg/chart/v2/util/save_test.go @@ -21,6 +21,7 @@ import ( "bytes" "compress/gzip" "crypto/sha256" + "encoding/binary" "encoding/hex" "errors" "io" @@ -35,6 +36,8 @@ import ( "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/chart/v2/loader" + + "github.com/stretchr/testify/require" ) func TestSave(t *testing.T) { @@ -130,6 +133,7 @@ func TestSave(t *testing.T) { } } +// https://github.com/helm/helm/issues/31844 func TestSavedGzipExtraFieldIsValid(t *testing.T) { tmp := t.TempDir() c := &chart.Chart{ @@ -161,18 +165,14 @@ func TestSavedGzipExtraFieldIsValid(t *testing.T) { // Each subfield consists of SI1, SI2 (1 byte each), // a 2-byte little-endian LEN, and LEN bytes of data. // https://www.rfc-editor.org/rfc/rfc1952.html#page-8 - // https://github.com/helm/helm/issues/31844 extra := r.Extra - if len(extra) > 0 { - if len(extra) < 4 { - t.Fatalf("gzip extra field too short to contain a valid subfield: %d byte(s)", len(extra)) - } - dataLen := int(extra[2]) | int(extra[3])<<8 - // Assume a single subfield - if len(extra) != 4+dataLen { - t.Errorf("gzip extra field has malformed subfield: LEN=%d but %d data byte(s) follow the subfield header", dataLen, len(extra)-4) - } - } + + require.NotEmpty(t, extra) + require.GreaterOrEqual(t, len(extra), 4) + + dataLen := int(binary.LittleEndian.Uint16(extra[2:4])) + // Assume a single subfield. + require.Lenf(t, extra, 4+dataLen, "gzip extra field has malformed subfield: LEN=%d but %d data byte(s) follow the subfield header", dataLen, len(extra)-4) } // Creates a copy with a different schema; does not modify anything. From 07faec5593aabbde72c357d9c10123b6a3da33c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:32:37 +0000 Subject: [PATCH 378/571] chore(deps): bump actions/setup-go from 6.4.0 to 6.5.0 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.4.0 to 6.5.0. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/4a3601121dd01d1626a1e23e37211e3254c1c06c...924ae3a1cded613372ab5595356fb5720e22ba16) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 6.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/release.yml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index a0fea406c..b4ce89f26 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -22,7 +22,7 @@ jobs: - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0 + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # pin@6.5.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 13ea569ef..e13d8950e 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -17,7 +17,7 @@ jobs: - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0 + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # pin@6.5.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index ba3714eae..93ab559fc 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -25,7 +25,7 @@ jobs: - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0 + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # pin@6.5.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 993c45899..ee2612917 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0 + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # pin@6.5.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true @@ -93,7 +93,7 @@ jobs: run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # pin@6.4.0 + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # pin@6.5.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true From 144246cd2893f92ee8e477c691837ea27e8c2733 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Mon, 22 Jun 2026 19:31:18 -0700 Subject: [PATCH 379/571] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: George Jenkins --- pkg/cli/environment_test.go | 3 +-- pkg/cli/values/options_test.go | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go index f1067f6c6..9303389a3 100644 --- a/pkg/cli/environment_test.go +++ b/pkg/cli/environment_test.go @@ -18,7 +18,6 @@ package cli import ( "os" - "reflect" "strings" "testing" @@ -137,7 +136,7 @@ func TestEnvSettings(t *testing.T) { assert.Equal(t, tt.kcontext, settings.KubeContext, "kube-context") assert.Equal(t, tt.maxhistory, settings.MaxHistory, "maxHistory") assert.Equal(t, tt.kubeAsUser, settings.KubeAsUser, "kubeAsUser") - assert.True(t, reflect.DeepEqual(tt.kubeAsGroups, settings.KubeAsGroups), "kubeAsGroups") + assert.Equal(t, tt.kubeAsGroups, settings.KubeAsGroups, "kubeAsGroups") assert.Equal(t, tt.kubeCaFile, settings.KubeCaFile, "kubeCaFile") assert.Equal(t, tt.burstLimit, settings.BurstLimit, "burstLimit") assert.Equal(t, tt.kubeInsecure, settings.KubeInsecureSkipTLSVerify, "kubeInsecure") diff --git a/pkg/cli/values/options_test.go b/pkg/cli/values/options_test.go index 3a33da7f1..f6a116f18 100644 --- a/pkg/cli/values/options_test.go +++ b/pkg/cli/values/options_test.go @@ -21,7 +21,6 @@ import ( "errors" "os" "path/filepath" - "reflect" "testing" "github.com/stretchr/testify/assert" @@ -214,7 +213,7 @@ func TestReadFile(t *testing.T) { // Test the function got, err := readFile(actualFilePath, tt.providers) - assert.NoError(t, err, "readFile() expected no error for stdin") + require.NoError(t, err, "readFile() expected no error for stdin") assert.Equal(t, testData, got) return } @@ -360,8 +359,8 @@ func TestMergeValuesCLI(t *testing.T) { assert.Error(t, err) return } - assert.NoError(t, err) - assert.True(t, reflect.DeepEqual(got, tt.expected), "MergeValues() = %v, want %v", got, tt.expected) + require.NoError(t, err) + assert.Equal(t, tt.expected, got) }) } } From f76136d39f8c1fd6110a23317786c4c53f6f9cb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 21:32:44 +0000 Subject: [PATCH 380/571] chore(deps): bump golangci/golangci-lint-action from 9.2.1 to 9.3.0 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 9.2.1 to 9.3.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/82606bf257cbaff209d206a39f5134f0cfbfd2ee...ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-version: 9.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/golangci-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index e13d8950e..6355db230 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -22,6 +22,6 @@ jobs: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true - name: golangci-lint - uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee #pin@9.2.1 + uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a #pin@9.3.0 with: version: ${{ env.GOLANGCI_LINT_VERSION }} From 5a9b8a788ff260c031f2d2f3e1b6903bd31b3496 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 21:33:40 +0000 Subject: [PATCH 381/571] chore(deps): bump github.com/fluxcd/cli-utils from 1.2.1 to 1.2.2 Bumps [github.com/fluxcd/cli-utils](https://github.com/fluxcd/cli-utils) from 1.2.1 to 1.2.2. - [Release notes](https://github.com/fluxcd/cli-utils/releases) - [Commits](https://github.com/fluxcd/cli-utils/compare/v1.2.1...v1.2.2) --- updated-dependencies: - dependency-name: github.com/fluxcd/cli-utils dependency-version: 1.2.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index ea6d6c972..d85d0387a 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.19.0 - github.com/fluxcd/cli-utils v1.2.1 + github.com/fluxcd/cli-utils v1.2.2 github.com/foxcpp/go-mockdns v1.2.0 github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.13.0 @@ -115,7 +115,7 @@ require ( github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/gomega v1.39.1 // indirect + github.com/onsi/gomega v1.42.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect @@ -158,7 +158,7 @@ require ( go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/mod v0.36.0 // indirect - golang.org/x/net v0.55.0 // indirect + golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.21.0 // indirect golang.org/x/sys v0.46.0 // indirect @@ -173,7 +173,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/component-base v0.36.2 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect - k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect + k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.21.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect diff --git a/go.sum b/go.sum index f50611b85..a92d35dfe 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/cli-utils v1.2.1 h1:ug9CicKW7H9QXnvNDapTSKuryZvWcu4Nw7pRvQa6jDY= -github.com/fluxcd/cli-utils v1.2.1/go.mod h1:cky6M6eHvTQkoPtsuFYLIgAMYdpTCSLoor4IA6vueSw= +github.com/fluxcd/cli-utils v1.2.2 h1:adDOmwE+LSwTzmYUaoEFPblruOuaQEKAg1ZNTmPJObE= +github.com/fluxcd/cli-utils v1.2.2/go.mod h1:FsghNGY+3Sr70c0FOB7I5So0kzoYVdvQ8GTid3XXVWM= github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -233,8 +233,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= -github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I= +github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -400,8 +400,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -505,8 +505,8 @@ k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hk k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= k8s.io/kubectl v0.36.2 h1:rpUGGpeL09XVOLep2yle5jrtk//JA1L6ZHfkQQtVEwk= k8s.io/kubectl v0.36.2/go.mod h1:gVbQ3B/yb4bSR2ggQ7rd0W6icUSWs7sduH4e16Vii+0= -k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= -k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc= +k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk= sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= From 301e3a12d55c279ef3bd0b1976bafd43bc1e0b7b Mon Sep 17 00:00:00 2001 From: kimsungmin1 Date: Thu, 28 Aug 2025 14:43:49 +0900 Subject: [PATCH 382/571] fix scope when helm push to a registry that use token auth Signed-off-by: kimsungmin1 --- go.mod | 2 + go.sum | 4 + pkg/registry/client.go | 9 ++ pkg/registry/client_http_test.go | 2 +- pkg/registry/client_insecure_tls_test.go | 2 +- pkg/registry/client_scope_test.go | 109 +++++++++++++++++++++++ pkg/registry/client_tls_test.go | 2 +- pkg/registry/registry_test.go | 33 +++++-- 8 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 pkg/registry/client_scope_test.go diff --git a/go.mod b/go.mod index d85d0387a..cb512b54b 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 + github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/rubenv/sql-migrate v1.8.1 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/spf13/cobra v1.10.2 @@ -80,6 +81,7 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.1 // indirect diff --git a/go.sum b/go.sum index a92d35dfe..0c237ab46 100644 --- a/go.sum +++ b/go.sum @@ -105,6 +105,8 @@ github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8b github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -241,6 +243,8 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 77f3d3efd..366251c60 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -717,6 +717,8 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu repository.PlainHTTP = c.plainHTTP repository.Client = c.authorizer + ctx = WithScopeHint(ctx, repository, auth.ActionPush, auth.ActionPull) + manifestDescriptor, err = oras.ExtendedCopy(ctx, memoryStore, parsedRef.String(), repository, parsedRef.String(), oras.DefaultExtendedCopyOptions) if err != nil { return nil, err @@ -927,3 +929,10 @@ func (c *Client) tagManifest(ctx context.Context, memoryStore *memory.Store, return oras.TagBytes(ctx, memoryStore, ocispec.MediaTypeImageManifest, manifestData, parsedRef.String()) } + +func WithScopeHint(ctx context.Context, target any, actions ...string) context.Context { + if repo, ok := target.(*remote.Repository); ok { + return auth.AppendRepositoryScope(ctx, repo.Reference, actions...) + } + return ctx +} diff --git a/pkg/registry/client_http_test.go b/pkg/registry/client_http_test.go index 3eb74c541..316a82d3f 100644 --- a/pkg/registry/client_http_test.go +++ b/pkg/registry/client_http_test.go @@ -30,7 +30,7 @@ type HTTPRegistryClientTestSuite struct { func (suite *HTTPRegistryClientTestSuite) SetupSuite() { // init test client - setup(&suite.TestRegistry, false, false) + setup(&suite.TestRegistry, false, false, "htpasswd") } func (suite *HTTPRegistryClientTestSuite) TearDownSuite() { diff --git a/pkg/registry/client_insecure_tls_test.go b/pkg/registry/client_insecure_tls_test.go index 8c4e928e4..ae0a44337 100644 --- a/pkg/registry/client_insecure_tls_test.go +++ b/pkg/registry/client_insecure_tls_test.go @@ -29,7 +29,7 @@ type InsecureTLSRegistryClientTestSuite struct { func (suite *InsecureTLSRegistryClientTestSuite) SetupSuite() { // init test client - setup(&suite.TestRegistry, true, true) + setup(&suite.TestRegistry, true, true, "htpasswd") } func (suite *InsecureTLSRegistryClientTestSuite) TearDownSuite() { diff --git a/pkg/registry/client_scope_test.go b/pkg/registry/client_scope_test.go new file mode 100644 index 000000000..245af5e3e --- /dev/null +++ b/pkg/registry/client_scope_test.go @@ -0,0 +1,109 @@ +/* +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 ( + "context" + "fmt" + "log" + "net/http" + "os" + "testing" + + "github.com/stretchr/testify/suite" +) + +type RegistryScopeTestSuite struct { + TestSuite +} + +func (suite *RegistryScopeTestSuite) SetupSuite() { + // set registry use token auth + dockerRegistry := setup(&suite.TestSuite, true, true, "token") + // Start Docker registry + go dockerRegistry.ListenAndServe() +} +func (suite *RegistryScopeTestSuite) TearDownSuite() { + teardown(&suite.TestSuite) + os.RemoveAll(suite.WorkspaceDir) +} + +func (suite *RegistryScopeTestSuite) Test_1_Cehck_Push_Request_Scope() { + + //set simple auth server to check the auth request scope + server := &http.Server{ + Addr: suite.AuthServerHost, + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + suite.Equal(string("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice"), r.URL.String()) + w.WriteHeader(http.StatusOK) + }), + } + go func() { + err := server.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + log.Fatalf("http server failed to ListenAndServe:%v", err) + } + }() + + // basic push, good ref + testingChartCreationTime := "1977-09-02T22:04:05Z" + chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") + suite.Nil(err, "no error loading test chart") + meta, err := extractChartMeta(chartData) + suite.Nil(err, "no error extracting chart meta") + ref := fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) + _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) + suite.NotNil(err, "error pushing good ref because auth server don't give proper token") + + //shutdown auth server + err = server.Shutdown(context.Background()) + suite.Nil(err, "shutdown simple auth server") + +} + +func (suite *RegistryScopeTestSuite) Test_2_Cehck_Pull_Request_Scope() { + + //set simple auth server to check the auth request scope + server := &http.Server{ + Addr: suite.AuthServerHost, + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + suite.Equal(string("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull&service=testservice"), r.URL.String()) + w.WriteHeader(http.StatusOK) + }), + } + go func() { + err := server.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + log.Fatalf("http server failed to ListenAndServe:%v", err) + } + }() + + // Load test chart (to build ref pushed in previous test) + // Simple pull, chart only + chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") + suite.Nil(err, "no error loading test chart") + meta, err := extractChartMeta(chartData) + suite.Nil(err, "no error extracting chart meta") + ref := fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) + _, err = suite.RegistryClient.Pull(ref) + suite.NotNil(err, "error pulling a simple chart because auth server don't give proper token") + + //shutdown auth server + err = server.Shutdown(context.Background()) + suite.Nil(err, "shutdown simple auth server") +} + +func TestRegistryScopeTestSuite(t *testing.T) { + suite.Run(t, new(RegistryScopeTestSuite)) +} diff --git a/pkg/registry/client_tls_test.go b/pkg/registry/client_tls_test.go index 2bea0377f..45eff232c 100644 --- a/pkg/registry/client_tls_test.go +++ b/pkg/registry/client_tls_test.go @@ -31,7 +31,7 @@ type TLSRegistryClientTestSuite struct { func (suite *TLSRegistryClientTestSuite) SetupSuite() { // init test client - setup(&suite.TestRegistry, true, false) + setup(&suite.TestRegistry, true, false, "htpasswd") } func (suite *TLSRegistryClientTestSuite) TearDownSuite() { diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index dc8118c6e..76d6fcb16 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -33,8 +33,10 @@ import ( "github.com/distribution/distribution/v3/configuration" "github.com/distribution/distribution/v3/registry" _ "github.com/distribution/distribution/v3/registry/auth/htpasswd" + _ "github.com/distribution/distribution/v3/registry/auth/token" _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/phayes/freeport" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "golang.org/x/crypto/bcrypt" @@ -55,6 +57,8 @@ var ( testHtpasswdFileBasename = "authtest.htpasswd" testUsername = "myuser" testPassword = "mypass" + testIssuer = "testissuer" + testService = "testservice" ) type TestRegistry struct { @@ -62,13 +66,14 @@ type TestRegistry struct { Out io.Writer FakeRegistryHost string DockerRegistryHost string + AuthServerHost string CompromisedRegistryHost string WorkspaceDir string RegistryClient *Client dockerRegistry *registry.Registry } -func setup(suite *TestRegistry, tlsEnabled, insecure bool) { +func setup(suite *TestRegistry, tlsEnabled, insecure bool, auth string) { suite.WorkspaceDir = testWorkspaceDir err := os.RemoveAll(suite.WorkspaceDir) require.NoError(suite.T(), err, "no error removing test workspace dir") @@ -139,11 +144,27 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool) { config.HTTP.DrainTimeout = time.Duration(10) * time.Second config.Storage = map[string]configuration.Parameters{"inmemory": map[string]any{}} - config.Auth = configuration.Auth{ - "htpasswd": configuration.Parameters{ - "realm": "localhost", - "path": htpasswdPath, - }, + if auth == "token" { + port, err := freeport.GetFreePort() + suite.Nil(err, "no error finding free port for test auth server") + + suite.AuthServerHost = fmt.Sprintf("localhost:%d", port) + + config.Auth = configuration.Auth{ + "token": configuration.Parameters{ + "realm": "http://" + suite.AuthServerHost + "/auth", + "service": testService, + "issuer": testIssuer, + "rootcertbundle": tlsServerCert, + }, + } + } else { + config.Auth = configuration.Auth{ + "htpasswd": configuration.Parameters{ + "realm": "localhost", + "path": htpasswdPath, + }, + } } // config tls From 319e06cda30e706db541066d5fe07249b0ed656c Mon Sep 17 00:00:00 2001 From: kimsungmin1 Date: Thu, 28 Aug 2025 14:53:14 +0900 Subject: [PATCH 383/571] add newline in license header Signed-off-by: kimsungmin1 --- pkg/registry/client_scope_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/registry/client_scope_test.go b/pkg/registry/client_scope_test.go index 245af5e3e..707e9eed6 100644 --- a/pkg/registry/client_scope_test.go +++ b/pkg/registry/client_scope_test.go @@ -1,9 +1,12 @@ /* 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. From fc743b2a1be47c69cc3bf58e0fdc023f8844f866 Mon Sep 17 00:00:00 2001 From: kimsungmin1 Date: Fri, 29 Aug 2025 09:34:11 +0900 Subject: [PATCH 384/571] remove freeport dependency Signed-off-by: kimsungmin1 --- go.mod | 1 - go.sum | 2 -- pkg/registry/registry_test.go | 7 ++++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index cb512b54b..ca3d23bb2 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,6 @@ require ( github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 - github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/rubenv/sql-migrate v1.8.1 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index 0c237ab46..2d8184ab0 100644 --- a/go.sum +++ b/go.sum @@ -243,8 +243,6 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= -github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index 76d6fcb16..5f745c8a1 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -36,7 +36,6 @@ import ( _ "github.com/distribution/distribution/v3/registry/auth/token" _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/phayes/freeport" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "golang.org/x/crypto/bcrypt" @@ -145,10 +144,12 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool, auth string) { config.Storage = map[string]configuration.Parameters{"inmemory": map[string]any{}} if auth == "token" { - port, err := freeport.GetFreePort() + ln, err := net.Listen("tcp", "127.0.0.1:0") suite.Nil(err, "no error finding free port for test auth server") + defer ln.Close() - suite.AuthServerHost = fmt.Sprintf("localhost:%d", port) + //set test auth server host + suite.AuthServerHost = ln.Addr().String() config.Auth = configuration.Auth{ "token": configuration.Parameters{ From 9d77ccd46ddf9b2675137891c159d1de35674072 Mon Sep 17 00:00:00 2001 From: kimsungmin1 Date: Tue, 9 Sep 2025 10:00:54 +0900 Subject: [PATCH 385/571] fix typo Signed-off-by: kimsungmin1 --- pkg/registry/client_scope_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/registry/client_scope_test.go b/pkg/registry/client_scope_test.go index 707e9eed6..37c77d7d3 100644 --- a/pkg/registry/client_scope_test.go +++ b/pkg/registry/client_scope_test.go @@ -42,7 +42,7 @@ func (suite *RegistryScopeTestSuite) TearDownSuite() { os.RemoveAll(suite.WorkspaceDir) } -func (suite *RegistryScopeTestSuite) Test_1_Cehck_Push_Request_Scope() { +func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { //set simple auth server to check the auth request scope server := &http.Server{ @@ -75,7 +75,7 @@ func (suite *RegistryScopeTestSuite) Test_1_Cehck_Push_Request_Scope() { } -func (suite *RegistryScopeTestSuite) Test_2_Cehck_Pull_Request_Scope() { +func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { //set simple auth server to check the auth request scope server := &http.Server{ From 8c8d6e3262be77783ef42a47c9593b6e42a3066a Mon Sep 17 00:00:00 2001 From: kimsungmin1 Date: Thu, 11 Dec 2025 00:49:09 +0900 Subject: [PATCH 386/571] change client_scope_test.go to use httptest Signed-off-by: kimsungmin1 --- pkg/registry/client_scope_test.go | 61 ++++++++++++------------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/pkg/registry/client_scope_test.go b/pkg/registry/client_scope_test.go index 37c77d7d3..e5a34214e 100644 --- a/pkg/registry/client_scope_test.go +++ b/pkg/registry/client_scope_test.go @@ -17,10 +17,10 @@ limitations under the License. package registry import ( - "context" "fmt" - "log" + "net" "net/http" + "net/http/httptest" "os" "testing" @@ -44,20 +44,17 @@ func (suite *RegistryScopeTestSuite) TearDownSuite() { func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { - //set simple auth server to check the auth request scope - server := &http.Server{ - Addr: suite.AuthServerHost, - Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - suite.Equal(string("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice"), r.URL.String()) - w.WriteHeader(http.StatusOK) - }), - } - go func() { - err := server.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - log.Fatalf("http server failed to ListenAndServe:%v", err) - } - }() + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + suite.Equal(string("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice"), r.URL.String()) + w.WriteHeader(http.StatusOK) + }) + listener, err := net.Listen("tcp", suite.AuthServerHost) + suite.Nil(err, "no error creating server listner") + + ts := httptest.NewUnstartedServer(handler) + ts.Listener = listener + ts.Start() + defer ts.Close() // basic push, good ref testingChartCreationTime := "1977-09-02T22:04:05Z" @@ -69,28 +66,21 @@ func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) suite.NotNil(err, "error pushing good ref because auth server don't give proper token") - //shutdown auth server - err = server.Shutdown(context.Background()) - suite.Nil(err, "shutdown simple auth server") - } func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { - //set simple auth server to check the auth request scope - server := &http.Server{ - Addr: suite.AuthServerHost, - Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - suite.Equal(string("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull&service=testservice"), r.URL.String()) - w.WriteHeader(http.StatusOK) - }), - } - go func() { - err := server.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - log.Fatalf("http server failed to ListenAndServe:%v", err) - } - }() + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + suite.Equal(string("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull&service=testservice"), r.URL.String()) + w.WriteHeader(http.StatusOK) + }) + listener, err := net.Listen("tcp", suite.AuthServerHost) + suite.Nil(err, "no error creating server listner") + + ts := httptest.NewUnstartedServer(handler) + ts.Listener = listener + ts.Start() + defer ts.Close() // Load test chart (to build ref pushed in previous test) // Simple pull, chart only @@ -102,9 +92,6 @@ func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { _, err = suite.RegistryClient.Pull(ref) suite.NotNil(err, "error pulling a simple chart because auth server don't give proper token") - //shutdown auth server - err = server.Shutdown(context.Background()) - suite.Nil(err, "shutdown simple auth server") } func TestRegistryScopeTestSuite(t *testing.T) { From 26e5071f6680272d773f0ca22ea946a35c4dce66 Mon Sep 17 00:00:00 2001 From: kimsungmin1 Date: Fri, 9 Jan 2026 20:10:24 +0900 Subject: [PATCH 387/571] change suite.Nil, suite.NotNill to more proper function(suite.NoError, suite.Error) Signed-off-by: kimsungmin1 --- pkg/registry/client_scope_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/registry/client_scope_test.go b/pkg/registry/client_scope_test.go index e5a34214e..2039faad9 100644 --- a/pkg/registry/client_scope_test.go +++ b/pkg/registry/client_scope_test.go @@ -49,7 +49,7 @@ func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { w.WriteHeader(http.StatusOK) }) listener, err := net.Listen("tcp", suite.AuthServerHost) - suite.Nil(err, "no error creating server listner") + suite.NoError(err, "no error creating server listner") ts := httptest.NewUnstartedServer(handler) ts.Listener = listener @@ -59,12 +59,12 @@ func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { // basic push, good ref testingChartCreationTime := "1977-09-02T22:04:05Z" chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") + suite.NoError(err, "no error loading test chart") meta, err := extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") + suite.NoError(err, "no error extracting chart meta") ref := fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) - suite.NotNil(err, "error pushing good ref because auth server don't give proper token") + suite.Error(err, "error pushing good ref because auth server don't give proper token") } @@ -75,7 +75,7 @@ func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { w.WriteHeader(http.StatusOK) }) listener, err := net.Listen("tcp", suite.AuthServerHost) - suite.Nil(err, "no error creating server listner") + suite.NoError(err, "no error creating server listner") ts := httptest.NewUnstartedServer(handler) ts.Listener = listener @@ -85,12 +85,12 @@ func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { // Load test chart (to build ref pushed in previous test) // Simple pull, chart only chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") + suite.NoError(err, "no error loading test chart") meta, err := extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") + suite.NoError(err, "no error extracting chart meta") ref := fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) _, err = suite.RegistryClient.Pull(ref) - suite.NotNil(err, "error pulling a simple chart because auth server don't give proper token") + suite.Error(err, "error pulling a simple chart because auth server don't give proper token") } From 530e728ea932109befc4a8165cd9a2bc78170c07 Mon Sep 17 00:00:00 2001 From: kimsm28 Date: Wed, 11 Mar 2026 10:56:03 +0900 Subject: [PATCH 388/571] fix typo, remove unnecessary code, fix to avoid to use the assertion in http hanlder Signed-off-by: kimsm28 --- pkg/registry/client.go | 11 +++++++++-- pkg/registry/client_scope_test.go | 21 +++++++++++++++------ pkg/registry/registry_test.go | 1 - 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 366251c60..1fe4b7813 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -717,7 +717,7 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu repository.PlainHTTP = c.plainHTTP repository.Client = c.authorizer - ctx = WithScopeHint(ctx, repository, auth.ActionPush, auth.ActionPull) + ctx = withScopeHint(ctx, repository, auth.ActionPull, auth.ActionPush) manifestDescriptor, err = oras.ExtendedCopy(ctx, memoryStore, parsedRef.String(), repository, parsedRef.String(), oras.DefaultExtendedCopyOptions) if err != nil { @@ -930,7 +930,14 @@ func (c *Client) tagManifest(ctx context.Context, memoryStore *memory.Store, manifestData, parsedRef.String()) } -func WithScopeHint(ctx context.Context, target any, actions ...string) context.Context { +// add actions when request a registry authentication token(jwt) +// example1. when we want to pull 'testrepo/local-subchart' we can send bellow url, and 'pull' is the action +// auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull&service=testservice +// example2. when we want to push 'testrepo/local-subchart' we can send bellow url, and 'pull%2Cpush' are the actions +// auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice +// we can set the actions like bellow +// example) ctx = WithScopeHint(ctx, repository, auth.ActionPush, auth.ActionPull) +func withScopeHint(ctx context.Context, target any, actions ...string) context.Context { if repo, ok := target.(*remote.Repository); ok { return auth.AppendRepositoryScope(ctx, repo.Reference, actions...) } diff --git a/pkg/registry/client_scope_test.go b/pkg/registry/client_scope_test.go index 2039faad9..2a3def6f6 100644 --- a/pkg/registry/client_scope_test.go +++ b/pkg/registry/client_scope_test.go @@ -44,12 +44,14 @@ func (suite *RegistryScopeTestSuite) TearDownSuite() { func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { + var requestUrl string + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - suite.Equal(string("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice"), r.URL.String()) + requestUrl = r.URL.String() w.WriteHeader(http.StatusOK) }) listener, err := net.Listen("tcp", suite.AuthServerHost) - suite.NoError(err, "no error creating server listner") + suite.NoError(err, "no error creating server listener") ts := httptest.NewUnstartedServer(handler) ts.Listener = listener @@ -64,18 +66,23 @@ func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { suite.NoError(err, "no error extracting chart meta") ref := fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) - suite.Error(err, "error pushing good ref because auth server don't give proper token") + suite.Error(err, "error pushing good ref because auth server doesn't give proper token") + + //check the url that authentication server received + suite.Equal("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice", requestUrl) } func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { + var requestUrl string + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - suite.Equal(string("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull&service=testservice"), r.URL.String()) + requestUrl = r.URL.String() w.WriteHeader(http.StatusOK) }) listener, err := net.Listen("tcp", suite.AuthServerHost) - suite.NoError(err, "no error creating server listner") + suite.NoError(err, "no error creating server listener") ts := httptest.NewUnstartedServer(handler) ts.Listener = listener @@ -90,8 +97,10 @@ func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { suite.NoError(err, "no error extracting chart meta") ref := fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) _, err = suite.RegistryClient.Pull(ref) - suite.Error(err, "error pulling a simple chart because auth server don't give proper token") + suite.Error(err, "error pulling a simple chart because auth server doesn't give proper token") + //check the url that authentication server received + suite.Equal("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull&service=testservice", requestUrl) } func TestRegistryScopeTestSuite(t *testing.T) { diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index 5f745c8a1..3dc65908a 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -146,7 +146,6 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool, auth string) { if auth == "token" { ln, err := net.Listen("tcp", "127.0.0.1:0") suite.Nil(err, "no error finding free port for test auth server") - defer ln.Close() //set test auth server host suite.AuthServerHost = ln.Addr().String() From 8883c9cc7006eef7e3d07836b2d5dd7ca1066168 Mon Sep 17 00:00:00 2001 From: kimsm28 Date: Wed, 11 Mar 2026 11:03:34 +0900 Subject: [PATCH 389/571] fix variable naming requestUrl -> requestURL Signed-off-by: kimsm28 --- pkg/registry/client_scope_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/registry/client_scope_test.go b/pkg/registry/client_scope_test.go index 2a3def6f6..a5f8ea16c 100644 --- a/pkg/registry/client_scope_test.go +++ b/pkg/registry/client_scope_test.go @@ -44,10 +44,10 @@ func (suite *RegistryScopeTestSuite) TearDownSuite() { func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { - var requestUrl string + var requestURL string handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestUrl = r.URL.String() + requestURL = r.URL.String() w.WriteHeader(http.StatusOK) }) listener, err := net.Listen("tcp", suite.AuthServerHost) @@ -69,16 +69,16 @@ func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { suite.Error(err, "error pushing good ref because auth server doesn't give proper token") //check the url that authentication server received - suite.Equal("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice", requestUrl) + suite.Equal("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice", requestURL) } func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { - var requestUrl string + var requestURL string handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestUrl = r.URL.String() + requestURL = r.URL.String() w.WriteHeader(http.StatusOK) }) listener, err := net.Listen("tcp", suite.AuthServerHost) @@ -100,7 +100,7 @@ func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { suite.Error(err, "error pulling a simple chart because auth server doesn't give proper token") //check the url that authentication server received - suite.Equal("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull&service=testservice", requestUrl) + suite.Equal("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull&service=testservice", requestURL) } func TestRegistryScopeTestSuite(t *testing.T) { From 1f0a72849657c3fe167320f7f5729c377f6dadf8 Mon Sep 17 00:00:00 2001 From: kimsm28 Date: Mon, 13 Apr 2026 13:15:49 +0900 Subject: [PATCH 390/571] fix registry test failures by adjusting DockerRegistryHost and auth server listener management - Change DockerRegistryHost to use 127.0.0.1 for HTTP tests and helm-test-registry for TLS tests to match certificate hostname - Add defer ln.Close() to prevent resource leak in token auth server setup - Restore requestURL variable and assertion in test body instead of handler to avoid potential race condition Signed-off-by: kimsm28 --- pkg/registry/client_scope_test.go | 3 --- pkg/registry/registry_test.go | 12 ++++++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/registry/client_scope_test.go b/pkg/registry/client_scope_test.go index a5f8ea16c..09ad2b0d0 100644 --- a/pkg/registry/client_scope_test.go +++ b/pkg/registry/client_scope_test.go @@ -45,7 +45,6 @@ func (suite *RegistryScopeTestSuite) TearDownSuite() { func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { var requestURL string - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requestURL = r.URL.String() w.WriteHeader(http.StatusOK) @@ -70,13 +69,11 @@ func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { //check the url that authentication server received suite.Equal("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice", requestURL) - } func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { var requestURL string - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requestURL = r.URL.String() w.WriteHeader(http.StatusOK) diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index 3dc65908a..d00a9f3c6 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -133,11 +133,14 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool, auth string) { suite.Require().NoError(err, "no error finding free port for test registry") defer func() { _ = ln.Close() }() - // Change the registry host to another host which is not localhost. - // This is required because Docker enforces HTTP if the registry - // host is localhost/127.0.0.1. + // Use localhost for HTTP tests and helm-test-registry for TLS tests. + // TLS tests need a different hostname to match the certificate. port := ln.Addr().(*net.TCPAddr).Port - suite.DockerRegistryHost = fmt.Sprintf("helm-test-registry:%d", port) + if tlsEnabled { + suite.DockerRegistryHost = fmt.Sprintf("helm-test-registry:%d", port) + } else { + suite.DockerRegistryHost = fmt.Sprintf("127.0.0.1:%d", port) + } config.HTTP.Addr = ln.Addr().String() config.HTTP.DrainTimeout = time.Duration(10) * time.Second @@ -146,6 +149,7 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool, auth string) { if auth == "token" { ln, err := net.Listen("tcp", "127.0.0.1:0") suite.Nil(err, "no error finding free port for test auth server") + defer ln.Close() //set test auth server host suite.AuthServerHost = ln.Addr().String() From ee181f4570550d61379bf59f2a69b993b74d49cf Mon Sep 17 00:00:00 2001 From: kimsm28 Date: Mon, 13 Apr 2026 13:29:21 +0900 Subject: [PATCH 391/571] fix typos in withScopeHint function comment - Change 'bellow' to 'below' (spelling correction in multiple places) - Change 'WithScopeHint' to 'withScopeHint' to match actual function name Signed-off-by: kimsm28 --- pkg/registry/client.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 1fe4b7813..e252ad2c5 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -931,12 +931,12 @@ func (c *Client) tagManifest(ctx context.Context, memoryStore *memory.Store, } // add actions when request a registry authentication token(jwt) -// example1. when we want to pull 'testrepo/local-subchart' we can send bellow url, and 'pull' is the action +// example1. when we want to pull 'testrepo/local-subchart' we can send below url, and 'pull' is the action // auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull&service=testservice -// example2. when we want to push 'testrepo/local-subchart' we can send bellow url, and 'pull%2Cpush' are the actions +// example2. when we want to push 'testrepo/local-subchart' we can send below url, and 'pull%2Cpush' are the actions // auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice -// we can set the actions like bellow -// example) ctx = WithScopeHint(ctx, repository, auth.ActionPush, auth.ActionPull) +// we can set the actions like below +// example) ctx = withScopeHint(ctx, repository, auth.ActionPush, auth.ActionPull) func withScopeHint(ctx context.Context, target any, actions ...string) context.Context { if repo, ok := target.(*remote.Repository); ok { return auth.AppendRepositoryScope(ctx, repo.Reference, actions...) From 9b7a70f0ca4bf0dcfd242962d9eef211188057dd Mon Sep 17 00:00:00 2001 From: kimsm28 Date: Mon, 13 Apr 2026 17:53:48 +0900 Subject: [PATCH 392/571] test: improve client_scope_test.go to avoid data races and brittle assertions - Fix data race by using channel instead of shared variable for requestURL - Make assertions more robust by parsing URL and checking query parameters instead of exact string matching Signed-off-by: kimsm28 --- pkg/registry/client_scope_test.go | 36 +++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/pkg/registry/client_scope_test.go b/pkg/registry/client_scope_test.go index 09ad2b0d0..b1da20a6b 100644 --- a/pkg/registry/client_scope_test.go +++ b/pkg/registry/client_scope_test.go @@ -21,8 +21,10 @@ import ( "net" "net/http" "net/http/httptest" + "net/url" "os" "testing" + "time" "github.com/stretchr/testify/suite" ) @@ -44,9 +46,9 @@ func (suite *RegistryScopeTestSuite) TearDownSuite() { func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { - var requestURL string + requestURL := make(chan string, 1) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestURL = r.URL.String() + requestURL <- r.URL.String() w.WriteHeader(http.StatusOK) }) listener, err := net.Listen("tcp", suite.AuthServerHost) @@ -68,14 +70,25 @@ func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { suite.Error(err, "error pushing good ref because auth server doesn't give proper token") //check the url that authentication server received - suite.Equal("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice", requestURL) + select { + case urlStr := <-requestURL: + u, err := url.Parse(urlStr) + suite.NoError(err, "no error parsing requested URL") + + suite.Equal("/auth", u.Path) + suite.Equal("testservice", u.Query().Get("service")) + scope := u.Query().Get("scope") + suite.Contains(scope, "repository:testrepo/local-subchart:pull,push") + case <-time.After(5 * time.Second): + suite.T().Fatal("timeout waiting for auth request") + } } func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { - var requestURL string + requestURL := make(chan string, 1) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestURL = r.URL.String() + requestURL <- r.URL.String() w.WriteHeader(http.StatusOK) }) listener, err := net.Listen("tcp", suite.AuthServerHost) @@ -97,7 +110,18 @@ func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { suite.Error(err, "error pulling a simple chart because auth server doesn't give proper token") //check the url that authentication server received - suite.Equal("/auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull&service=testservice", requestURL) + select { + case urlStr := <-requestURL: + u, err := url.Parse(urlStr) + suite.NoError(err, "no error parsing requested URL") + + suite.Equal("/auth", u.Path) + suite.Equal("testservice", u.Query().Get("service")) + scope := u.Query().Get("scope") + suite.Contains(scope, "repository:testrepo/local-subchart:pull") + case <-time.After(5 * time.Second): + suite.T().Fatal("timeout waiting for auth request") + } } func TestRegistryScopeTestSuite(t *testing.T) { From 26ada49c1ad50af5e11dbb8a65b703251295c689 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Mon, 25 May 2026 08:45:09 -0600 Subject: [PATCH 393/571] Update pkg/registry/client.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Terry Howe --- pkg/registry/client.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pkg/registry/client.go b/pkg/registry/client.go index e252ad2c5..4d7da78dc 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -937,9 +937,6 @@ func (c *Client) tagManifest(ctx context.Context, memoryStore *memory.Store, // auth?scope=repository%3Atestrepo%2Flocal-subchart%3Apull%2Cpush&service=testservice // we can set the actions like below // example) ctx = withScopeHint(ctx, repository, auth.ActionPush, auth.ActionPull) -func withScopeHint(ctx context.Context, target any, actions ...string) context.Context { - if repo, ok := target.(*remote.Repository); ok { - return auth.AppendRepositoryScope(ctx, repo.Reference, actions...) - } - return ctx +func withScopeHint(ctx context.Context, repo *remote.Repository, actions ...string) context.Context { + return auth.AppendRepositoryScope(ctx, repo.Reference, actions...) } From 3cbee7e935eb6e783c36b17d6eb79eb6edcda15b Mon Sep 17 00:00:00 2001 From: kimsm28 Date: Thu, 2 Jul 2026 21:49:33 +0900 Subject: [PATCH 394/571] fix(registry): use plain-http registry in token-auth scope test The scope test configured a TLS registry while advertising an http token realm. oras refuses to send credentials to an http token realm when the registry itself was contacted over https, so the auth server was never reached and the test timed out ("timeout waiting for auth request"). Use a plain-http registry so the registry and token-realm schemes match; the scope carried in the token request is what this test verifies, and that is independent of TLS. Also make the auth handler's channel send non-blocking so that a client retry can never block the handler and stall the push/pull flow, which addresses the review comment about a potential deadlock. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: kimsm28 --- pkg/registry/client_scope_test.go | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/pkg/registry/client_scope_test.go b/pkg/registry/client_scope_test.go index b1da20a6b..98cdc74ff 100644 --- a/pkg/registry/client_scope_test.go +++ b/pkg/registry/client_scope_test.go @@ -30,17 +30,18 @@ import ( ) type RegistryScopeTestSuite struct { - TestSuite + TestRegistry } func (suite *RegistryScopeTestSuite) SetupSuite() { - // set registry use token auth - dockerRegistry := setup(&suite.TestSuite, true, true, "token") - // Start Docker registry - go dockerRegistry.ListenAndServe() + // Set up a plain-HTTP registry that uses token auth. The token realm is + // served over http (see setup), so the registry must be contacted over + // http as well: oras refuses to send credentials to an http token realm + // when the registry itself was reached over https. + setup(&suite.TestRegistry, false, false, "token") } func (suite *RegistryScopeTestSuite) TearDownSuite() { - teardown(&suite.TestSuite) + teardown(&suite.TestRegistry) os.RemoveAll(suite.WorkspaceDir) } @@ -48,7 +49,13 @@ func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { requestURL := make(chan string, 1) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestURL <- r.URL.String() + // Capture only the first auth request; never block the handler if the + // client happens to retry, so the auth server always responds and the + // push/pull flow can't deadlock waiting on us. + select { + case requestURL <- r.URL.String(): + default: + } w.WriteHeader(http.StatusOK) }) listener, err := net.Listen("tcp", suite.AuthServerHost) @@ -88,7 +95,13 @@ func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { requestURL := make(chan string, 1) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestURL <- r.URL.String() + // Capture only the first auth request; never block the handler if the + // client happens to retry, so the auth server always responds and the + // push/pull flow can't deadlock waiting on us. + select { + case requestURL <- r.URL.String(): + default: + } w.WriteHeader(http.StatusOK) }) listener, err := net.Listen("tcp", suite.AuthServerHost) From 4e9ca06856b1aeba65c4d4cd8e4dce4c83445776 Mon Sep 17 00:00:00 2001 From: kimsm28 Date: Thu, 2 Jul 2026 21:49:33 +0900 Subject: [PATCH 395/571] chore: go mod tidy after rebase on main Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: kimsm28 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ca3d23bb2..628b3f374 100644 --- a/go.mod +++ b/go.mod @@ -80,7 +80,7 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect - github.com/go-jose/go-jose/v4 v4.0.5 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.1 // indirect diff --git a/go.sum b/go.sum index 2d8184ab0..dc469fde9 100644 --- a/go.sum +++ b/go.sum @@ -105,8 +105,8 @@ github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8b github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= -github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= -github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= From 503acff97580aa803f5d639fb1fbb06ad1226c31 Mon Sep 17 00:00:00 2001 From: kimsm28 Date: Thu, 2 Jul 2026 21:57:38 +0900 Subject: [PATCH 396/571] fix(registry): resolve golangci-lint issues in token-auth tests - Use (*net.ListenConfig).Listen with the test context instead of net.Listen (noctx). - Use suite.NoError instead of suite.Nil for the error check (testifylint). - Drop the unnecessary leading blank lines in the scope test funcs (whitespace). Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: kimsm28 --- pkg/registry/client_scope_test.go | 8 ++++---- pkg/registry/registry_test.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/registry/client_scope_test.go b/pkg/registry/client_scope_test.go index 98cdc74ff..f5f32ecb1 100644 --- a/pkg/registry/client_scope_test.go +++ b/pkg/registry/client_scope_test.go @@ -46,7 +46,6 @@ func (suite *RegistryScopeTestSuite) TearDownSuite() { } func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { - requestURL := make(chan string, 1) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Capture only the first auth request; never block the handler if the @@ -58,7 +57,8 @@ func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { } w.WriteHeader(http.StatusOK) }) - listener, err := net.Listen("tcp", suite.AuthServerHost) + lnCfg := net.ListenConfig{} + listener, err := lnCfg.Listen(suite.T().Context(), "tcp", suite.AuthServerHost) suite.NoError(err, "no error creating server listener") ts := httptest.NewUnstartedServer(handler) @@ -92,7 +92,6 @@ func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { } func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { - requestURL := make(chan string, 1) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Capture only the first auth request; never block the handler if the @@ -104,7 +103,8 @@ func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { } w.WriteHeader(http.StatusOK) }) - listener, err := net.Listen("tcp", suite.AuthServerHost) + lnCfg := net.ListenConfig{} + listener, err := lnCfg.Listen(suite.T().Context(), "tcp", suite.AuthServerHost) suite.NoError(err, "no error creating server listener") ts := httptest.NewUnstartedServer(handler) diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index d00a9f3c6..9388c8142 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -147,8 +147,8 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool, auth string) { config.Storage = map[string]configuration.Parameters{"inmemory": map[string]any{}} if auth == "token" { - ln, err := net.Listen("tcp", "127.0.0.1:0") - suite.Nil(err, "no error finding free port for test auth server") + ln, err := lnCfg.Listen(suite.T().Context(), "tcp", "127.0.0.1:0") + suite.NoError(err, "no error finding free port for test auth server") defer ln.Close() //set test auth server host From e9eda8c5988d74172862b3b72cb1f8edf2226191 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 21:33:08 +0000 Subject: [PATCH 397/571] chore(deps): bump github/codeql-action/upload-sarif Bumps [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) from 4.36.2 to 4.36.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...54f647b7e1bb85c95cddabcd46b0c578ec92bc1a) --- updated-dependencies: - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.36.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 03419cbda..e90e91bb3 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 with: sarif_file: results.sarif From d539556a8d03c48258d08207aaf918c202c90df6 Mon Sep 17 00:00:00 2001 From: kimsungmin1 <142377392+kimsungmin1@users.noreply.github.com> Date: Fri, 3 Jul 2026 21:28:56 +0900 Subject: [PATCH 398/571] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: kimsungmin1 <142377392+kimsungmin1@users.noreply.github.com> --- pkg/registry/registry_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index 9388c8142..7d7b5a112 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -148,7 +148,7 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool, auth string) { if auth == "token" { ln, err := lnCfg.Listen(suite.T().Context(), "tcp", "127.0.0.1:0") - suite.NoError(err, "no error finding free port for test auth server") + suite.Require().NoError(err, "no error finding free port for test auth server") defer ln.Close() //set test auth server host From 143631f7356d7f2250540c044ae20277f495cc2f Mon Sep 17 00:00:00 2001 From: Mahesh Sadupalli Date: Fri, 3 Jul 2026 20:28:01 +0200 Subject: [PATCH 399/571] fix(engine): prevent Files.Lines panic on empty file Files.Lines guards against a nil entry but an empty file inside a chart is stored as a non-nil zero-length byte slice, so the trailing newline check indexes s[-1] and panics. The engine recovers the panic into a render error, making every template/install/upgrade/lint that references the file fail. Extend the guard to len(f[path]) == 0 and return an empty slice, matching the behaviour for missing files. Fixes #32279 Signed-off-by: Mahesh Sadupalli --- pkg/engine/files.go | 2 +- pkg/engine/files_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/pkg/engine/files.go b/pkg/engine/files.go index 7834cac2c..eeb04d76c 100644 --- a/pkg/engine/files.go +++ b/pkg/engine/files.go @@ -154,7 +154,7 @@ func (f files) AsSecrets() string { // {{ range .Files.Lines "foo/bar.html" }} // {{ . }}{{ end }} func (f files) Lines(path string) []string { - if f == nil || f[path] == nil { + if f == nil || len(f[path]) == 0 { return []string{} } s := string(f[path]) diff --git a/pkg/engine/files_test.go b/pkg/engine/files_test.go index f07f6afeb..b4318a597 100644 --- a/pkg/engine/files_test.go +++ b/pkg/engine/files_test.go @@ -30,6 +30,8 @@ var cases = []struct { {"story/author.txt", "Joseph Conrad"}, {"multiline/test.txt", "bar\nfoo\n"}, {"multiline/test_with_blank_lines.txt", "bar\nfoo\n\n\n"}, + {"empty/empty.txt", ""}, + {"empty/newline_only.txt", "\n"}, } func getTestFiles() files { @@ -109,3 +111,31 @@ func TestBlankLines(t *testing.T) { as.Equal("bar", out[0]) as.Empty(out[3]) } + +func TestLinesEmptyFile(t *testing.T) { + as := assert.New(t) + + f := getTestFiles() + + out := f.Lines("empty/empty.txt") + as.Empty(out) +} + +func TestLinesNewlineOnlyFile(t *testing.T) { + as := assert.New(t) + + f := getTestFiles() + + out := f.Lines("empty/newline_only.txt") + as.Len(out, 1) + as.Empty(out[0]) +} + +func TestLinesMissingFile(t *testing.T) { + as := assert.New(t) + + f := getTestFiles() + + out := f.Lines("nonexistent.txt") + as.Empty(out) +} From 07259ecc86521db556c4a18331241bbefc6bb6b2 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 6 Jul 2026 08:06:02 +0200 Subject: [PATCH 400/571] chore: fix several checks issues from gocritic Signed-off-by: Matthieu MOREL --- .golangci.yml | 13 ----- internal/chart/v3/lint/rules/crds_test.go | 6 +-- internal/chart/v3/lint/rules/values_test.go | 2 +- internal/chart/v3/loader/directory.go | 2 +- internal/chart/v3/loader/load_test.go | 2 +- internal/chart/v3/util/chartfile.go | 2 +- internal/chart/v3/util/create.go | 6 +-- internal/chart/v3/util/expand.go | 4 +- internal/chart/v3/util/save.go | 6 +-- internal/chart/v3/util/save_test.go | 2 +- internal/fileutil/fileutil_test.go | 12 ++--- internal/plugin/installer/extractor.go | 8 ++-- internal/plugin/installer/http_installer.go | 6 +-- .../plugin/installer/http_installer_test.go | 48 +++++++++---------- internal/plugin/installer/installer.go | 2 +- internal/plugin/installer/local_installer.go | 6 +-- .../plugin/installer/local_installer_test.go | 10 ++-- internal/plugin/installer/oci_installer.go | 12 ++--- .../plugin/installer/oci_installer_test.go | 18 +++---- .../plugin/installer/plugin_structure_test.go | 20 ++++---- .../plugin/installer/vcs_installer_test.go | 2 +- .../plugin/installer/verification_test.go | 12 ++--- internal/plugin/sign_test.go | 4 +- internal/plugin/verify_test.go | 14 +++--- internal/sympath/walk_test.go | 2 +- internal/test/test.go | 2 +- internal/third_party/dep/fs/fs.go | 2 +- internal/third_party/dep/fs/fs_test.go | 38 +++++++-------- pkg/action/action.go | 4 +- pkg/action/dependency_test.go | 4 +- pkg/action/install.go | 8 ++-- pkg/action/package.go | 2 +- pkg/action/package_test.go | 2 +- pkg/action/pull.go | 2 +- pkg/chart/loader/load_test.go | 2 +- pkg/chart/v2/lint/rules/crds_test.go | 6 +-- pkg/chart/v2/lint/rules/values_test.go | 2 +- pkg/chart/v2/loader/directory.go | 2 +- pkg/chart/v2/loader/load_test.go | 2 +- pkg/chart/v2/util/chartfile.go | 2 +- pkg/chart/v2/util/create.go | 6 +-- pkg/chart/v2/util/expand.go | 4 +- pkg/chart/v2/util/save.go | 6 +-- pkg/chart/v2/util/save_test.go | 2 +- pkg/cli/values/options_test.go | 4 +- pkg/cmd/docs.go | 2 +- pkg/cmd/load_plugins.go | 5 +- pkg/cmd/plugin_package.go | 4 +- pkg/cmd/plugin_package_test.go | 12 ++--- pkg/cmd/plugin_uninstall_test.go | 10 ++-- pkg/cmd/plugin_verify_test.go | 10 ++-- pkg/cmd/pull_test.go | 6 +-- pkg/cmd/repo_add_test.go | 2 +- pkg/cmd/repo_remove.go | 2 +- pkg/cmd/template.go | 6 +-- pkg/downloader/cache.go | 4 +- pkg/downloader/cache_test.go | 2 +- pkg/downloader/chart_downloader.go | 6 +-- pkg/downloader/chart_downloader_test.go | 4 +- pkg/downloader/manager.go | 8 ++-- pkg/downloader/manager_test.go | 12 ++--- pkg/engine/engine.go | 2 +- pkg/getter/httpgetter_test.go | 2 +- pkg/pusher/ocipusher_test.go | 6 +-- pkg/registry/registry_test.go | 9 ++-- pkg/repo/v1/chartrepo.go | 8 ++-- pkg/repo/v1/index_test.go | 4 +- pkg/repo/v1/repo.go | 2 +- pkg/repo/v1/repo_test.go | 2 +- pkg/storage/driver/cfgmaps.go | 2 +- pkg/storage/driver/util.go | 2 +- pkg/strvals/literal_parser_test.go | 7 +-- pkg/strvals/parser.go | 4 +- pkg/strvals/parser_test.go | 17 +++---- 74 files changed, 236 insertions(+), 249 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index b3ced6f1a..518e47052 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -94,32 +94,19 @@ linters: - emptyStringTest - exposedSyncMutex - filepathJoin - - httpNoBody - hugeParam - ifElseChain - importShadow - nestingReduce - nilValReturn - - octalLiteral - paramTypeCombine - - preferStringWriter - ptrToRefParam - rangeValCopy - - regexpSimplify - singleCaseSwitch - - sloppyReassign - - sloppyLen - - sprintfQuotedString - - stringConcatSimplify - - stringsCompare - - stringXbytes - todoCommentWithoutDetail - tooManyResultsChecker - - typeAssertChain - uncheckedInlineErr - unnamedResult - - unnecessaryDefer - - unslice - whyNoLint enable-all: true diff --git a/internal/chart/v3/lint/rules/crds_test.go b/internal/chart/v3/lint/rules/crds_test.go index b80339d7b..60cb1b5ba 100644 --- a/internal/chart/v3/lint/rules/crds_test.go +++ b/internal/chart/v3/lint/rules/crds_test.go @@ -45,11 +45,11 @@ func TestCrdWithEmptyDocument(t *testing.T) { `apiVersion: v1 name: test version: 0.1.0 -`), 0644) +`), 0o644) // CRD with comments before --- (creates empty document) crdsDir := filepath.Join(chartDir, "crds") - os.Mkdir(crdsDir, 0755) + os.Mkdir(crdsDir, 0o755) os.WriteFile(filepath.Join(crdsDir, "test.yaml"), []byte( `# Comments create empty document --- @@ -57,7 +57,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: test.example.io -`), 0644) +`), 0o644) linter := support.Linter{ChartDir: chartDir} Crds(&linter) diff --git a/internal/chart/v3/lint/rules/values_test.go b/internal/chart/v3/lint/rules/values_test.go index 54c7e6457..c0d2f5299 100644 --- a/internal/chart/v3/lint/rules/values_test.go +++ b/internal/chart/v3/lint/rules/values_test.go @@ -176,7 +176,7 @@ func TestValidateValuesFile(t *testing.T) { func createTestingSchema(t *testing.T, dir string) string { t.Helper() schemafile := filepath.Join(dir, "values.schema.json") - if err := os.WriteFile(schemafile, []byte(testSchema), 0700); err != nil { + if err := os.WriteFile(schemafile, []byte(testSchema), 0o700); err != nil { t.Fatalf("Failed to write schema to tmpdir: %s", err) } return schemafile diff --git a/internal/chart/v3/loader/directory.go b/internal/chart/v3/loader/directory.go index dfe3af3b2..7cf343568 100644 --- a/internal/chart/v3/loader/directory.go +++ b/internal/chart/v3/loader/directory.go @@ -114,7 +114,7 @@ func LoadDir(dir string) (*chart.Chart, error) { files = append(files, &archive.BufferedFile{Name: n, ModTime: fi.ModTime(), Data: data}) return nil } - if err = sympath.Walk(topdir, walk); err != nil { + if err := sympath.Walk(topdir, walk); err != nil { return c, err } diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index de9219a1b..a493104bf 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -377,7 +377,7 @@ func TestLoadInvalidArchive(t *testing.T) { h := &tar.Header{ Name: internalPath, - Mode: 0755, + Mode: 0o755, Size: int64(len(body)), ModTime: time.Now(), } diff --git a/internal/chart/v3/util/chartfile.go b/internal/chart/v3/util/chartfile.go index 25271e1cf..76dec9d47 100644 --- a/internal/chart/v3/util/chartfile.go +++ b/internal/chart/v3/util/chartfile.go @@ -58,7 +58,7 @@ func SaveChartfile(filename string, cf *chart.Metadata) error { if err != nil { return err } - return os.WriteFile(filename, out, 0644) + return os.WriteFile(filename, out, 0o644) } // IsChartDir validate a chart directory. diff --git a/internal/chart/v3/util/create.go b/internal/chart/v3/util/create.go index b3d75ac2d..dcb5bbb39 100644 --- a/internal/chart/v3/util/create.go +++ b/internal/chart/v3/util/create.go @@ -803,7 +803,7 @@ func Create(name, dir string) (string, error) { } } // Need to add the ChartsDir explicitly as it does not contain any file OOTB - if err := os.MkdirAll(filepath.Join(cdir, ChartsDir), 0755); err != nil { + if err := os.MkdirAll(filepath.Join(cdir, ChartsDir), 0o755); err != nil { return cdir, err } return cdir, nil @@ -816,10 +816,10 @@ func transform(src, replacement string) []byte { } func writeFile(name string, content []byte) error { - if err := os.MkdirAll(filepath.Dir(name), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(name), 0o755); err != nil { return err } - return os.WriteFile(name, content, 0644) + return os.WriteFile(name, content, 0o644) } func validateChartName(name string) error { diff --git a/internal/chart/v3/util/expand.go b/internal/chart/v3/util/expand.go index 5b057b8e0..df0652849 100644 --- a/internal/chart/v3/util/expand.go +++ b/internal/chart/v3/util/expand.go @@ -88,11 +88,11 @@ func Expand(dir string, r io.Reader) error { // Make sure the necessary subdirs get created. basedir := filepath.Dir(outpath) - if err := os.MkdirAll(basedir, 0755); err != nil { + if err := os.MkdirAll(basedir, 0o755); err != nil { return err } - if err := os.WriteFile(outpath, file.Data, 0644); err != nil { + if err := os.WriteFile(outpath, file.Data, 0o644); err != nil { return err } } diff --git a/internal/chart/v3/util/save.go b/internal/chart/v3/util/save.go index f886c6175..942e736ed 100644 --- a/internal/chart/v3/util/save.go +++ b/internal/chart/v3/util/save.go @@ -49,7 +49,7 @@ func SaveDir(c *chart.Chart, dest string) error { if fi, err := os.Stat(outdir); err == nil && !fi.IsDir() { return fmt.Errorf("file %s already exists and is not a directory", outdir) } - if err := os.MkdirAll(outdir, 0755); err != nil { + if err := os.MkdirAll(outdir, 0o755); err != nil { return err } @@ -115,7 +115,7 @@ func Save(c *chart.Chart, outDir string) (string, error) { dir := filepath.Dir(filename) if stat, err := os.Stat(dir); err != nil { if errors.Is(err, fs.ErrNotExist) { - if err2 := os.MkdirAll(dir, 0755); err2 != nil { + if err2 := os.MkdirAll(dir, 0o755); err2 != nil { return "", err2 } } else { @@ -230,7 +230,7 @@ func writeToTar(out *tar.Writer, name string, body []byte, modTime time.Time) er // TODO: Do we need to create dummy parent directory names if none exist? h := &tar.Header{ Name: filepath.ToSlash(name), - Mode: 0644, + Mode: 0o644, Size: int64(len(body)), ModTime: modTime, } diff --git a/internal/chart/v3/util/save_test.go b/internal/chart/v3/util/save_test.go index 34e7d898e..cbe9b853f 100644 --- a/internal/chart/v3/util/save_test.go +++ b/internal/chart/v3/util/save_test.go @@ -258,7 +258,7 @@ func TestSaveDir(t *testing.T) { tmp2 := t.TempDir() c.Metadata.Name = "../ahab" pth := filepath.Join(tmp2, "tmpcharts") - if err := os.MkdirAll(filepath.Join(pth), 0755); err != nil { + if err := os.MkdirAll(filepath.Join(pth), 0o755); err != nil { t.Fatal(err) } diff --git a/internal/fileutil/fileutil_test.go b/internal/fileutil/fileutil_test.go index 07cbe7695..c78f70a3d 100644 --- a/internal/fileutil/fileutil_test.go +++ b/internal/fileutil/fileutil_test.go @@ -35,7 +35,7 @@ func TestAtomicWriteFile(t *testing.T) { testpath := filepath.Join(dir, "test") stringContent := "Test content" reader := bytes.NewReader([]byte(stringContent)) - mode := os.FileMode(0644) + mode := os.FileMode(0o644) err := AtomicWriteFile(testpath, reader, mode) assert.NoError(t, err) @@ -56,7 +56,7 @@ func TestAtomicWriteFile_CreateTempError(t *testing.T) { invalidPath := "/invalid/path/that/does/not/exist/testfile" reader := bytes.NewReader([]byte("test content")) - mode := os.FileMode(0644) + mode := os.FileMode(0o644) err := AtomicWriteFile(invalidPath, reader, mode) assert.Error(t, err, "Expected error when CreateTemp fails") @@ -68,7 +68,7 @@ func TestAtomicWriteFile_EmptyContent(t *testing.T) { testpath := filepath.Join(dir, "empty_helm") reader := bytes.NewReader([]byte("")) - mode := os.FileMode(0644) + mode := os.FileMode(0o644) err := AtomicWriteFile(testpath, reader, mode) assert.NoError(t, err, "AtomicWriteFile error with empty content") @@ -87,7 +87,7 @@ func TestAtomicWriteFile_LargeContent(t *testing.T) { // Create a large content string largeContent := strings.Repeat("HELM", 1024*1024) reader := bytes.NewReader([]byte(largeContent)) - mode := os.FileMode(0644) + mode := os.FileMode(0o644) err := AtomicWriteFile(testpath, reader, mode) assert.NoError(t, err, "AtomicWriteFile error with large content") @@ -105,10 +105,10 @@ func TestPlatformAtomicWriteFile_OverwritesExisting(t *testing.T) { path := filepath.Join(dir, "overwrite_test") first := bytes.NewReader([]byte("first")) - require.NoError(t, PlatformAtomicWriteFile(path, first, 0644), "first write failed") + require.NoError(t, PlatformAtomicWriteFile(path, first, 0o644), "first write failed") second := bytes.NewReader([]byte("second")) - require.NoError(t, PlatformAtomicWriteFile(path, second, 0644), "second write failed") + require.NoError(t, PlatformAtomicWriteFile(path, second, 0o644), "second write failed") contents, err := os.ReadFile(path) require.NoError(t, err, "failed reading result") diff --git a/internal/plugin/installer/extractor.go b/internal/plugin/installer/extractor.go index 3af56ab56..85d695bd2 100644 --- a/internal/plugin/installer/extractor.go +++ b/internal/plugin/installer/extractor.go @@ -132,7 +132,7 @@ func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error { return err } - if err := os.MkdirAll(targetDir, 0755); err != nil { + if err := os.MkdirAll(targetDir, 0o755); err != nil { return err } @@ -153,12 +153,12 @@ func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error { switch header.Typeflag { case tar.TypeDir: - if err := os.MkdirAll(path, 0755); err != nil { + if err := os.MkdirAll(path, 0o755); err != nil { return err } case tar.TypeReg: // Ensure parent directory exists - if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return err } outFile, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) @@ -189,6 +189,6 @@ func stripPluginName(name string) string { break } } - re := regexp.MustCompile(`(.*)-[0-9]+\..*`) + re := regexp.MustCompile(`(.*)-\d+\..*`) return re.ReplaceAllString(strippedName, `$1`) } diff --git a/internal/plugin/installer/http_installer.go b/internal/plugin/installer/http_installer.go index 5a2912d2e..92d3d686e 100644 --- a/internal/plugin/installer/http_installer.go +++ b/internal/plugin/installer/http_installer.go @@ -93,10 +93,10 @@ func (i *HTTPInstaller) Install() error { } filename := fmt.Sprintf("%s-%s.tgz", metadata.Name, metadata.Version) tarballPath := helmpath.DataPath("plugins", filename) - if err := os.MkdirAll(filepath.Dir(tarballPath), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(tarballPath), 0o755); err != nil { return fmt.Errorf("failed to create plugins directory: %w", err) } - if err := os.WriteFile(tarballPath, i.pluginData, 0644); err != nil { + if err := os.WriteFile(tarballPath, i.pluginData, 0o644); err != nil { return fmt.Errorf("failed to save tarball: %w", err) } @@ -112,7 +112,7 @@ func (i *HTTPInstaller) Install() error { // Save prov file if we have the data if i.provData != nil { provPath := tarballPath + ".prov" - if err := os.WriteFile(provPath, i.provData, 0644); err != nil { + if err := os.WriteFile(provPath, i.provData, 0o644); err != nil { slog.Debug("failed to save provenance file", "error", err) } } diff --git a/internal/plugin/installer/http_installer_test.go b/internal/plugin/installer/http_installer_test.go index efbca90c9..b080aa2ef 100644 --- a/internal/plugin/installer/http_installer_test.go +++ b/internal/plugin/installer/http_installer_test.go @@ -85,7 +85,7 @@ func TestHTTPInstaller(t *testing.T) { defer srv.Close() source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { + if err := os.MkdirAll(helmpath.DataPath("plugins"), 0o755); err != nil { t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) } @@ -132,7 +132,7 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) { defer srv.Close() source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { + if err := os.MkdirAll(helmpath.DataPath("plugins"), 0o755); err != nil { t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) } @@ -164,7 +164,7 @@ func TestHTTPInstallerUpdate(t *testing.T) { source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" ensure.HelmHome(t) - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { + if err := os.MkdirAll(helmpath.DataPath("plugins"), 0o755); err != nil { t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) } @@ -219,8 +219,8 @@ func TestExtract(t *testing.T) { Name, Body string Mode int64 }{ - {"plugin.yaml", "plugin metadata", 0600}, - {"README.md", "some text", 0777}, + {"plugin.yaml", "plugin metadata", 0o600}, + {"README.md", "some text", 0o777}, } for _, file := range files { hdr := &tar.Header{ @@ -271,8 +271,8 @@ func TestExtract(t *testing.T) { } // Calculate expected permissions after umask is applied - expectedPluginYAMLPerm := os.FileMode(0600 &^ currentUmask) - expectedReadmePerm := os.FileMode(0777 &^ currentUmask) + expectedPluginYAMLPerm := os.FileMode(0o600 &^ currentUmask) + expectedReadmePerm := os.FileMode(0o777 &^ currentUmask) pluginYAMLFullPath := filepath.Join(tempDir, "plugin.yaml") if info, err := os.Stat(pluginYAMLFullPath); err != nil { @@ -361,13 +361,13 @@ func TestExtractWithNestedDirectories(t *testing.T) { Mode int64 TypeFlag byte }{ - {"plugin.yaml", "plugin metadata", 0600, tar.TypeReg}, - {"bin/", "", 0755, tar.TypeDir}, - {"bin/plugin", "#!/usr/bin/env sh\necho plugin", 0755, tar.TypeReg}, - {"docs/", "", 0755, tar.TypeDir}, - {"docs/README.md", "readme content", 0644, tar.TypeReg}, - {"docs/examples/", "", 0755, tar.TypeDir}, - {"docs/examples/example1.yaml", "example content", 0644, tar.TypeReg}, + {"plugin.yaml", "plugin metadata", 0o600, tar.TypeReg}, + {"bin/", "", 0o755, tar.TypeDir}, + {"bin/plugin", "#!/usr/bin/env sh\necho plugin", 0o755, tar.TypeReg}, + {"docs/", "", 0o755, tar.TypeDir}, + {"docs/README.md", "readme content", 0o644, tar.TypeReg}, + {"docs/examples/", "", 0o755, tar.TypeDir}, + {"docs/examples/example1.yaml", "example content", 0o644, tar.TypeReg}, } for _, file := range files { @@ -434,13 +434,13 @@ func TestExtractWithExistingDirectory(t *testing.T) { // Pre-create the cache directory structure cacheDir := filepath.Join(tempDir, "cache") - if err := os.MkdirAll(filepath.Join(cacheDir, "existing", "dir"), 0755); err != nil { + if err := os.MkdirAll(filepath.Join(cacheDir, "existing", "dir"), 0o755); err != nil { t.Fatal(err) } // Create a file in the existing directory existingFile := filepath.Join(cacheDir, "existing", "file.txt") - if err := os.WriteFile(existingFile, []byte("existing content"), 0644); err != nil { + if err := os.WriteFile(existingFile, []byte("existing content"), 0o644); err != nil { t.Fatal(err) } @@ -453,10 +453,10 @@ func TestExtractWithExistingDirectory(t *testing.T) { Mode int64 TypeFlag byte }{ - {"plugin.yaml", "plugin metadata", 0600, tar.TypeReg}, - {"existing/", "", 0755, tar.TypeDir}, - {"existing/dir/", "", 0755, tar.TypeDir}, - {"existing/dir/newfile.txt", "new content", 0644, tar.TypeReg}, + {"plugin.yaml", "plugin metadata", 0o600, tar.TypeReg}, + {"existing/", "", 0o755, tar.TypeDir}, + {"existing/dir/", "", 0o755, tar.TypeDir}, + {"existing/dir/newfile.txt", "new content", 0o644, tar.TypeReg}, } for _, file := range files { @@ -523,10 +523,10 @@ func TestExtractPluginInSubdirectory(t *testing.T) { Mode int64 TypeFlag byte }{ - {"my-plugin/", "", 0755, tar.TypeDir}, - {"my-plugin/plugin.yaml", "name: my-plugin\nversion: 1.0.0\nusage: test\ndescription: test plugin\ncommand: $HELM_PLUGIN_DIR/bin/my-plugin", 0644, tar.TypeReg}, - {"my-plugin/bin/", "", 0755, tar.TypeDir}, - {"my-plugin/bin/my-plugin", "#!/usr/bin/env sh\necho test", 0755, tar.TypeReg}, + {"my-plugin/", "", 0o755, tar.TypeDir}, + {"my-plugin/plugin.yaml", "name: my-plugin\nversion: 1.0.0\nusage: test\ndescription: test plugin\ncommand: $HELM_PLUGIN_DIR/bin/my-plugin", 0o644, tar.TypeReg}, + {"my-plugin/bin/", "", 0o755, tar.TypeDir}, + {"my-plugin/bin/my-plugin", "#!/usr/bin/env sh\necho test", 0o755, tar.TypeReg}, } for _, file := range files { diff --git a/internal/plugin/installer/installer.go b/internal/plugin/installer/installer.go index a7ca35ad4..865570773 100644 --- a/internal/plugin/installer/installer.go +++ b/internal/plugin/installer/installer.go @@ -73,7 +73,7 @@ type VerificationResult struct { // InstallWithOptions installs a plugin with options. func InstallWithOptions(i Installer, opts Options) (*VerificationResult, error) { - if err := os.MkdirAll(filepath.Dir(i.Path()), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(i.Path()), 0o755); err != nil { return nil, err } if _, pathErr := os.Stat(i.Path()); !os.IsNotExist(pathErr) { diff --git a/internal/plugin/installer/local_installer.go b/internal/plugin/installer/local_installer.go index 71407380f..8d067612f 100644 --- a/internal/plugin/installer/local_installer.go +++ b/internal/plugin/installer/local_installer.go @@ -117,10 +117,10 @@ func (i *LocalInstaller) installFromArchive() error { } filename := fmt.Sprintf("%s-%s.tgz", metadata.Name, metadata.Version) tarballPath := helmpath.DataPath("plugins", filename) - if err := os.MkdirAll(filepath.Dir(tarballPath), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(tarballPath), 0o755); err != nil { return fmt.Errorf("failed to create plugins directory: %w", err) } - if err := os.WriteFile(tarballPath, data, 0644); err != nil { + if err := os.WriteFile(tarballPath, data, 0o644); err != nil { return fmt.Errorf("failed to save tarball: %w", err) } @@ -128,7 +128,7 @@ func (i *LocalInstaller) installFromArchive() error { provSource := i.Source + ".prov" if provData, err := os.ReadFile(provSource); err == nil { provPath := tarballPath + ".prov" - if err := os.WriteFile(provPath, provData, 0644); err != nil { + if err := os.WriteFile(provPath, provData, 0o644); err != nil { slog.Debug("failed to save provenance file", "error", err) } } diff --git a/internal/plugin/installer/local_installer_test.go b/internal/plugin/installer/local_installer_test.go index 510c2880b..02d4d7bff 100644 --- a/internal/plugin/installer/local_installer_test.go +++ b/internal/plugin/installer/local_installer_test.go @@ -34,7 +34,7 @@ func TestLocalInstaller(t *testing.T) { ensure.HelmHome(t) // Make a temp dir tdir := t.TempDir() - if err := os.WriteFile(filepath.Join(tdir, "plugin.yaml"), []byte{}, 0644); err != nil { + if err := os.WriteFile(filepath.Join(tdir, "plugin.yaml"), []byte{}, 0o644); err != nil { t.Fatal(err) } @@ -51,7 +51,7 @@ func TestLocalInstaller(t *testing.T) { if i.Path() != helmpath.DataPath("plugins", "echo-v1") { t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) } - defer os.RemoveAll(filepath.Dir(helmpath.DataPath())) // helmpath.DataPath is like /tmp/helm013130971/helm + os.RemoveAll(filepath.Dir(helmpath.DataPath())) // helmpath.DataPath is like /tmp/helm013130971/helm } func TestLocalInstallerNotAFolder(t *testing.T) { @@ -87,8 +87,8 @@ func TestLocalInstallerTarball(t *testing.T) { Body string Mode int64 }{ - {"test-plugin/plugin.yaml", "name: test-plugin\napiVersion: v1\ntype: cli/v1\nruntime: subprocess\nversion: 1.0.0\nconfig:\n shortHelp: test\n longHelp: test\nruntimeConfig:\n platformCommand:\n - command: echo", 0644}, - {"test-plugin/bin/test-plugin", "#!/usr/bin/env sh\necho test", 0755}, + {"test-plugin/plugin.yaml", "name: test-plugin\napiVersion: v1\ntype: cli/v1\nruntime: subprocess\nversion: 1.0.0\nconfig:\n shortHelp: test\n longHelp: test\nruntimeConfig:\n platformCommand:\n - command: echo", 0o644}, + {"test-plugin/bin/test-plugin", "#!/usr/bin/env sh\necho test", 0o755}, } for _, file := range files { @@ -113,7 +113,7 @@ func TestLocalInstallerTarball(t *testing.T) { } // Write tarball to file - if err := os.WriteFile(tarballPath, buf.Bytes(), 0644); err != nil { + if err := os.WriteFile(tarballPath, buf.Bytes(), 0o644); err != nil { t.Fatal(err) } diff --git a/internal/plugin/installer/oci_installer.go b/internal/plugin/installer/oci_installer.go index 50d01522a..383ddb914 100644 --- a/internal/plugin/installer/oci_installer.go +++ b/internal/plugin/installer/oci_installer.go @@ -104,10 +104,10 @@ func (i *OCIInstaller) Install() error { filename := fmt.Sprintf("%s-%s.tgz", metadata.Name, metadata.Version) tarballPath := helmpath.DataPath("plugins", filename) - if err := os.MkdirAll(filepath.Dir(tarballPath), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(tarballPath), 0o755); err != nil { return fmt.Errorf("failed to create plugins directory: %w", err) } - if err := os.WriteFile(tarballPath, i.pluginData, 0644); err != nil { + if err := os.WriteFile(tarballPath, i.pluginData, 0o644); err != nil { return fmt.Errorf("failed to save tarball: %w", err) } @@ -123,7 +123,7 @@ func (i *OCIInstaller) Install() error { // Save prov file if we have the data if i.provData != nil { provPath := tarballPath + ".prov" - if err := os.WriteFile(provPath, i.provData, 0644); err != nil { + if err := os.WriteFile(provPath, i.provData, 0o644); err != nil { slog.Debug("failed to save provenance file", "error", err) } } @@ -134,7 +134,7 @@ func (i *OCIInstaller) Install() error { } // Create cache directory - if err := os.MkdirAll(i.CacheDir, 0755); err != nil { + if err := os.MkdirAll(i.CacheDir, 0o755); err != nil { return fmt.Errorf("failed to create cache directory: %w", err) } @@ -229,12 +229,12 @@ func extractTar(r io.Reader, targetDir string) error { switch header.Typeflag { case tar.TypeDir: - if err := os.MkdirAll(path, 0755); err != nil { + if err := os.MkdirAll(path, 0o755); err != nil { return err } case tar.TypeReg: dir := filepath.Dir(path) - if err := os.MkdirAll(dir, 0755); err != nil { + if err := os.MkdirAll(dir, 0o755); err != nil { return err } diff --git a/internal/plugin/installer/oci_installer_test.go b/internal/plugin/installer/oci_installer_test.go index 1f25f4e76..650e46add 100644 --- a/internal/plugin/installer/oci_installer_test.go +++ b/internal/plugin/installer/oci_installer_test.go @@ -58,7 +58,7 @@ command: "$HELM_PLUGIN_DIR/bin/%s" `, pluginName, pluginName) header := &tar.Header{ Name: "plugin.yaml", - Mode: 0644, + Mode: 0o644, Size: int64(len(pluginYAML)), Typeflag: tar.TypeReg, } @@ -72,7 +72,7 @@ command: "$HELM_PLUGIN_DIR/bin/%s" // Add bin directory dirHeader := &tar.Header{ Name: "bin/", - Mode: 0755, + Mode: 0o755, Typeflag: tar.TypeDir, } if err := tarWriter.WriteHeader(dirHeader); err != nil { @@ -83,7 +83,7 @@ command: "$HELM_PLUGIN_DIR/bin/%s" execContent := fmt.Sprintf("#!/bin/sh\necho '%s test plugin'", pluginName) execHeader := &tar.Header{ Name: "bin/" + pluginName, - Mode: 0755, + Mode: 0o755, Size: int64(len(execContent)), Typeflag: tar.TypeReg, } @@ -539,7 +539,7 @@ func TestOCIInstaller_Install_ComponentExtraction(t *testing.T) { execPath := filepath.Join(tempDir, "bin", pluginName) if info, err := os.Stat(execPath); err != nil { t.Errorf("executable not found: %v", err) - } else if info.Mode()&0111 == 0 { + } else if info.Mode()&0o111 == 0 { t.Error("file is not executable") } @@ -561,7 +561,7 @@ func TestExtractTarGz(t *testing.T) { testContent := "test content" header := &tar.Header{ Name: "test-file.txt", - Mode: 0644, + Mode: 0o644, Size: int64(len(testContent)), Typeflag: tar.TypeReg, } @@ -577,7 +577,7 @@ func TestExtractTarGz(t *testing.T) { // Add a test directory dirHeader := &tar.Header{ Name: "test-dir/", - Mode: 0755, + Mode: 0o755, Typeflag: tar.TypeDir, } @@ -634,7 +634,7 @@ func TestExtractTar_UnknownFileType(t *testing.T) { testContent := "test content" header := &tar.Header{ Name: "test-file.txt", - Mode: 0644, + Mode: 0o644, Size: int64(len(testContent)), Typeflag: tar.TypeReg, } @@ -650,7 +650,7 @@ func TestExtractTar_UnknownFileType(t *testing.T) { // Test unknown file type unknownHeader := &tar.Header{ Name: "unknown-type", - Mode: 0644, + Mode: 0o644, Typeflag: tar.TypeSymlink, // Use a type that's not handled } @@ -686,7 +686,7 @@ func TestExtractTar_SuccessfulExtraction(t *testing.T) { testContent := "test content" header := &tar.Header{ Name: "test-file.txt", - Mode: 0644, + Mode: 0o644, Size: int64(len(testContent)), Typeflag: tar.TypeReg, } diff --git a/internal/plugin/installer/plugin_structure_test.go b/internal/plugin/installer/plugin_structure_test.go index c8766ce59..182eb9bd8 100644 --- a/internal/plugin/installer/plugin_structure_test.go +++ b/internal/plugin/installer/plugin_structure_test.go @@ -31,7 +31,7 @@ func TestDetectPluginRoot(t *testing.T) { { name: "plugin.yaml at root", setup: func(dir string) error { - return os.WriteFile(filepath.Join(dir, "plugin.yaml"), []byte("name: test"), 0644) + return os.WriteFile(filepath.Join(dir, "plugin.yaml"), []byte("name: test"), 0o644) }, expectRoot: ".", expectError: false, @@ -40,10 +40,10 @@ func TestDetectPluginRoot(t *testing.T) { name: "plugin.yaml in subdirectory", setup: func(dir string) error { subdir := filepath.Join(dir, "my-plugin") - if err := os.MkdirAll(subdir, 0755); err != nil { + if err := os.MkdirAll(subdir, 0o755); err != nil { return err } - return os.WriteFile(filepath.Join(subdir, "plugin.yaml"), []byte("name: test"), 0644) + return os.WriteFile(filepath.Join(subdir, "plugin.yaml"), []byte("name: test"), 0o644) }, expectRoot: "my-plugin", expectError: false, @@ -51,7 +51,7 @@ func TestDetectPluginRoot(t *testing.T) { { name: "no plugin.yaml", setup: func(dir string) error { - return os.WriteFile(filepath.Join(dir, "README.md"), []byte("test"), 0644) + return os.WriteFile(filepath.Join(dir, "README.md"), []byte("test"), 0o644) }, expectRoot: "", expectError: true, @@ -60,10 +60,10 @@ func TestDetectPluginRoot(t *testing.T) { name: "plugin.yaml in nested subdirectory (should not find)", setup: func(dir string) error { subdir := filepath.Join(dir, "outer", "inner") - if err := os.MkdirAll(subdir, 0755); err != nil { + if err := os.MkdirAll(subdir, 0o755); err != nil { return err } - return os.WriteFile(filepath.Join(subdir, "plugin.yaml"), []byte("name: test"), 0644) + return os.WriteFile(filepath.Join(subdir, "plugin.yaml"), []byte("name: test"), 0o644) }, expectRoot: "", expectError: true, @@ -110,14 +110,14 @@ func TestValidatePluginName(t *testing.T) { name: "matching directory and plugin name", setup: func(dir string) error { subdir := filepath.Join(dir, "my-plugin") - if err := os.MkdirAll(subdir, 0755); err != nil { + if err := os.MkdirAll(subdir, 0o755); err != nil { return err } yaml := `name: my-plugin version: 1.0.0 usage: test description: test` - return os.WriteFile(filepath.Join(subdir, "plugin.yaml"), []byte(yaml), 0644) + return os.WriteFile(filepath.Join(subdir, "plugin.yaml"), []byte(yaml), 0o644) }, pluginRoot: "my-plugin", expectedName: "my-plugin", @@ -127,14 +127,14 @@ description: test` name: "different directory and plugin name", setup: func(dir string) error { subdir := filepath.Join(dir, "wrong-name") - if err := os.MkdirAll(subdir, 0755); err != nil { + if err := os.MkdirAll(subdir, 0o755); err != nil { return err } yaml := `name: my-plugin version: 1.0.0 usage: test description: test` - return os.WriteFile(filepath.Join(subdir, "plugin.yaml"), []byte(yaml), 0644) + return os.WriteFile(filepath.Join(subdir, "plugin.yaml"), []byte(yaml), 0o644) }, pluginRoot: "wrong-name", expectedName: "wrong-name", diff --git a/internal/plugin/installer/vcs_installer_test.go b/internal/plugin/installer/vcs_installer_test.go index 54f94c724..401f2b921 100644 --- a/internal/plugin/installer/vcs_installer_test.go +++ b/internal/plugin/installer/vcs_installer_test.go @@ -52,7 +52,7 @@ func (r *testRepo) UpdateVersion(version string) error { func TestVCSInstaller(t *testing.T) { ensure.HelmHome(t) - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { + if err := os.MkdirAll(helmpath.DataPath("plugins"), 0o755); err != nil { t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) } diff --git a/internal/plugin/installer/verification_test.go b/internal/plugin/installer/verification_test.go index e05cda7fd..d3824a0fc 100644 --- a/internal/plugin/installer/verification_test.go +++ b/internal/plugin/installer/verification_test.go @@ -291,7 +291,7 @@ func TestInstallWithOptions_VerifyProvenanceAccessError(t *testing.T) { // Create a .prov file but make it inaccessible (simulate permission error) provFile := pluginTgz + ".prov" - if err := os.WriteFile(provFile, []byte("test"), 0000); err != nil { + if err := os.WriteFile(provFile, []byte("test"), 0o000); err != nil { t.Fatalf("Failed to create inaccessible provenance file: %v", err) } defer os.Remove(provFile) @@ -333,7 +333,7 @@ func createTestPluginDir(t *testing.T) string { // Create temporary directory with plugin structure tmpDir := t.TempDir() pluginDir := filepath.Join(tmpDir, "test-plugin") - if err := os.MkdirAll(pluginDir, 0755); err != nil { + if err := os.MkdirAll(pluginDir, 0o755); err != nil { t.Fatalf("Failed to create plugin directory: %v", err) } @@ -346,7 +346,7 @@ version: 1.0.0 runtimeConfig: platformCommand: - command: echo` - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginYaml), 0644); err != nil { + if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginYaml), 0o644); err != nil { t.Fatalf("Failed to create plugin.yaml: %v", err) } @@ -404,7 +404,7 @@ Version: GnuPG v1 iQEcBAEBCAAGBQJktest... -----END PGP SIGNATURE----- `, hashStr) - if err := os.WriteFile(provFile, []byte(provContent), 0644); err != nil { + if err := os.WriteFile(provFile, []byte(provContent), 0o644); err != nil { t.Fatalf("Failed to create provenance file: %v", err) } } @@ -414,7 +414,7 @@ func createProvFileInvalidFormat(t *testing.T, provFile string) { // Create an invalid provenance file (not PGP signed format) invalidProv := "This is not a valid PGP signed message" - if err := os.WriteFile(provFile, []byte(invalidProv), 0644); err != nil { + if err := os.WriteFile(provFile, []byte(invalidProv), 0o644); err != nil { t.Fatalf("Failed to create invalid provenance file: %v", err) } } @@ -427,7 +427,7 @@ func createTestKeyring(t *testing.T) string { keyringPath := filepath.Join(tmpDir, "pubring.gpg") // Create empty keyring for testing - if err := os.WriteFile(keyringPath, []byte{}, 0644); err != nil { + if err := os.WriteFile(keyringPath, []byte{}, 0o644); err != nil { t.Fatalf("Failed to create test keyring: %v", err) } diff --git a/internal/plugin/sign_test.go b/internal/plugin/sign_test.go index fce2dbeb3..7e97a82aa 100644 --- a/internal/plugin/sign_test.go +++ b/internal/plugin/sign_test.go @@ -28,7 +28,7 @@ func TestSignPlugin(t *testing.T) { // Create a test plugin directory tempDir := t.TempDir() pluginDir := filepath.Join(tempDir, "test-plugin") - if err := os.MkdirAll(pluginDir, 0755); err != nil { + if err := os.MkdirAll(pluginDir, 0o755); err != nil { t.Fatal(err) } @@ -41,7 +41,7 @@ version: 1.0.0 runtimeConfig: platformCommand: - command: echo` - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginYAML), 0644); err != nil { + if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginYAML), 0o644); err != nil { t.Fatal(err) } diff --git a/internal/plugin/verify_test.go b/internal/plugin/verify_test.go index 9c907788f..2828d30b6 100644 --- a/internal/plugin/verify_test.go +++ b/internal/plugin/verify_test.go @@ -41,11 +41,11 @@ func TestVerifyPlugin(t *testing.T) { // Create plugin directory pluginDir := filepath.Join(tempDir, "verify-test-plugin") - if err := os.MkdirAll(pluginDir, 0755); err != nil { + if err := os.MkdirAll(pluginDir, 0o755); err != nil { t.Fatal(err) } - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0644); err != nil { + if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644); err != nil { t.Fatal(err) } @@ -86,7 +86,7 @@ func TestVerifyPlugin(t *testing.T) { // Write the signature to .prov file provFile := tarballPath + ".prov" - if err := os.WriteFile(provFile, []byte(sig), 0644); err != nil { + if err := os.WriteFile(provFile, []byte(sig), 0o644); err != nil { t.Fatal(err) } @@ -126,11 +126,11 @@ func TestVerifyPluginBadSignature(t *testing.T) { // Create a plugin tarball pluginDir := filepath.Join(tempDir, "bad-plugin") - if err := os.MkdirAll(pluginDir, 0755); err != nil { + if err := os.MkdirAll(pluginDir, 0o755); err != nil { t.Fatal(err) } - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0644); err != nil { + if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644); err != nil { t.Fatal(err) } @@ -158,7 +158,7 @@ InvalidSignatureData -----END PGP SIGNATURE-----` provFile := tarballPath + ".prov" - if err := os.WriteFile(provFile, []byte(badSig), 0644); err != nil { + if err := os.WriteFile(provFile, []byte(badSig), 0o644); err != nil { t.Fatal(err) } @@ -185,7 +185,7 @@ func TestVerifyPluginMissingProvenance(t *testing.T) { tarballPath := filepath.Join(tempDir, "no-prov.tar.gz") // Create a minimal tarball - if err := os.WriteFile(tarballPath, []byte("dummy"), 0644); err != nil { + if err := os.WriteFile(tarballPath, []byte("dummy"), 0o644); err != nil { t.Fatal(err) } diff --git a/internal/sympath/walk_test.go b/internal/sympath/walk_test.go index b5bffbdab..76ed39cac 100644 --- a/internal/sympath/walk_test.go +++ b/internal/sympath/walk_test.go @@ -90,7 +90,7 @@ func makeTree(t *testing.T) { fd.Close() } } else { - require.NoError(t, os.Mkdir(path, 0770), "makeTree") + require.NoError(t, os.Mkdir(path, 0o770), "makeTree") } }) } diff --git a/internal/test/test.go b/internal/test/test.go index 202e015ab..ba4ea41e7 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -87,7 +87,7 @@ func update(filename string, in []byte) error { if !*updateGolden { return nil } - return os.WriteFile(filename, normalize(in), 0666) + return os.WriteFile(filename, normalize(in), 0o666) } func normalize(in []byte) []byte { diff --git a/internal/third_party/dep/fs/fs.go b/internal/third_party/dep/fs/fs.go index 3140cf91e..2607c73ac 100644 --- a/internal/third_party/dep/fs/fs.go +++ b/internal/third_party/dep/fs/fs.go @@ -193,7 +193,7 @@ func CopyFile(src, dst string) (err error) { } // Check for write errors on Close - if err = out.Close(); err != nil { + if err := out.Close(); err != nil { return err } diff --git a/internal/third_party/dep/fs/fs_test.go b/internal/third_party/dep/fs/fs_test.go index 806e33c28..6eed3f055 100644 --- a/internal/third_party/dep/fs/fs_test.go +++ b/internal/third_party/dep/fs/fs_test.go @@ -59,12 +59,12 @@ func TestRenameWithFallback(t *testing.T) { } srcpath = filepath.Join(dir, "a") - if err := os.MkdirAll(srcpath, 0777); err != nil { + if err := os.MkdirAll(srcpath, 0o777); err != nil { t.Fatal(err) } dstpath := filepath.Join(dir, "b") - if err := os.MkdirAll(dstpath, 0777); err != nil { + if err := os.MkdirAll(dstpath, 0o777); err != nil { t.Fatal(err) } @@ -77,7 +77,7 @@ func TestCopyDir(t *testing.T) { dir := t.TempDir() srcdir := filepath.Join(dir, "src") - if err := os.MkdirAll(srcdir, 0755); err != nil { + if err := os.MkdirAll(srcdir, 0o755); err != nil { t.Fatal(err) } @@ -94,7 +94,7 @@ func TestCopyDir(t *testing.T) { for i, file := range files { fn := filepath.Join(srcdir, file.path) dn := filepath.Dir(fn) - if err := os.MkdirAll(dn, 0755); err != nil { + if err := os.MkdirAll(dn, 0o755); err != nil { t.Fatal(err) } @@ -103,7 +103,7 @@ func TestCopyDir(t *testing.T) { t.Fatal(err) } - if _, err = fh.Write([]byte(file.contents)); err != nil { + if _, err = fh.WriteString(file.contents); err != nil { t.Fatal(err) } fh.Close() @@ -171,7 +171,7 @@ func TestCopyDirFail_SrcInaccessible(t *testing.T) { cleanup := setupInaccessibleDir(t, func(dir string) error { srcdir = filepath.Join(dir, "src") - return os.MkdirAll(srcdir, 0755) + return os.MkdirAll(srcdir, 0o755) }) defer cleanup() @@ -203,7 +203,7 @@ func TestCopyDirFail_DstInaccessible(t *testing.T) { dir := t.TempDir() srcdir = filepath.Join(dir, "src") - if err := os.MkdirAll(srcdir, 0755); err != nil { + if err := os.MkdirAll(srcdir, 0o755); err != nil { t.Fatal(err) } @@ -247,12 +247,12 @@ func TestCopyDirFail_DstExists(t *testing.T) { dir := t.TempDir() srcdir = filepath.Join(dir, "src") - if err = os.MkdirAll(srcdir, 0755); err != nil { + if err = os.MkdirAll(srcdir, 0o755); err != nil { t.Fatal(err) } dstdir = filepath.Join(dir, "dst") - if err = os.MkdirAll(dstdir, 0755); err != nil { + if err = os.MkdirAll(dstdir, 0o755); err != nil { t.Fatal(err) } @@ -288,7 +288,7 @@ func TestCopyDirFailOpen(t *testing.T) { dir := t.TempDir() srcdir = filepath.Join(dir, "src") - if err := os.MkdirAll(srcdir, 0755); err != nil { + if err := os.MkdirAll(srcdir, 0o755); err != nil { t.Fatal(err) } @@ -300,7 +300,7 @@ func TestCopyDirFailOpen(t *testing.T) { srcf.Close() // setup source file so that it cannot be read - if err = os.Chmod(srcfn, 0222); err != nil { + if err = os.Chmod(srcfn, 0o222); err != nil { t.Fatal(err) } @@ -320,7 +320,7 @@ func TestCopyFile(t *testing.T) { } want := "hello world" - if _, err := srcf.Write([]byte(want)); err != nil { + if _, err := srcf.WriteString(want); err != nil { t.Fatal(err) } srcf.Close() @@ -433,7 +433,7 @@ func TestCopyFileFail(t *testing.T) { cleanup := setupInaccessibleDir(t, func(dir string) error { dstdir = filepath.Join(dir, "dir") - return os.Mkdir(dstdir, 0777) + return os.Mkdir(dstdir, 0o777) }) defer cleanup() @@ -463,12 +463,12 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { subdir := filepath.Join(dir, "dir") cleanup := func() { - if err := os.Chmod(subdir, 0777); err != nil { + if err := os.Chmod(subdir, 0o777); err != nil { t.Error(err) } } - if err := os.Mkdir(subdir, 0777); err != nil { + if err := os.Mkdir(subdir, 0o777); err != nil { cleanup() t.Fatal(err) return nil @@ -480,7 +480,7 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { return nil } - if err := os.Chmod(subdir, 0666); err != nil { + if err := os.Chmod(subdir, 0o666); err != nil { cleanup() t.Fatal(err) return nil @@ -506,7 +506,7 @@ func TestIsDir(t *testing.T) { cleanup := setupInaccessibleDir(t, func(dir string) error { dn = filepath.Join(dir, "dir") - return os.Mkdir(dn, 0777) + return os.Mkdir(dn, 0o777) }) defer cleanup() @@ -552,7 +552,7 @@ func TestIsSymlink(t *testing.T) { dir := t.TempDir() dirPath := filepath.Join(dir, "directory") - if err := os.MkdirAll(dirPath, 0777); err != nil { + if err := os.MkdirAll(dirPath, 0o777); err != nil { t.Fatal(err) } @@ -582,7 +582,7 @@ func TestIsSymlink(t *testing.T) { inaccessibleFile = filepath.Join(dir, "file") if fh, err := os.Create(inaccessibleFile); err != nil { return err - } else if err = fh.Close(); err != nil { + } else if err := fh.Close(); err != nil { return err } diff --git a/pkg/action/action.go b/pkg/action/action.go index 6e5d8c15a..8105083f9 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -484,9 +484,9 @@ func (cfg *Configuration) renderResources(ctx context.Context, ch *chart.Chart, if includeCrds { for _, crd := range ch.CRDObjects() { if outputDir == "" { - fmt.Fprintf(b, "---\n# Source: %s\n%s\n", crd.Filename, string(crd.File.Data[:])) + fmt.Fprintf(b, "---\n# Source: %s\n%s\n", crd.Filename, string(crd.File.Data)) } else { - err = writeToFile(outputDir, crd.Filename, string(crd.File.Data[:]), fileWritten[crd.Filename]) + err = writeToFile(outputDir, crd.Filename, string(crd.File.Data), fileWritten[crd.Filename]) if err != nil { return hs, b, "", err } diff --git a/pkg/action/dependency_test.go b/pkg/action/dependency_test.go index 5be7bf5a9..3899c20fc 100644 --- a/pkg/action/dependency_test.go +++ b/pkg/action/dependency_test.go @@ -70,7 +70,7 @@ func TestDependencyStatus_Dashes(t *testing.T) { dir := t.TempDir() chartpath := filepath.Join(dir, "charts") - if err := os.MkdirAll(chartpath, 0700); err != nil { + if err := os.MkdirAll(chartpath, 0o700); err != nil { t.Fatal(err) } @@ -104,7 +104,7 @@ func TestStatArchiveForStatus(t *testing.T) { dir := t.TempDir() chartpath := filepath.Join(dir, "charts") - if err := os.MkdirAll(chartpath, 0700); err != nil { + if err := os.MkdirAll(chartpath, 0o700); err != nil { t.Fatal(err) } diff --git a/pkg/action/install.go b/pkg/action/install.go index fcfae7214..d36d2fc3e 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -68,7 +68,7 @@ import ( // since there can be filepath in front of it. const notesFileSuffix = "NOTES.txt" -const defaultDirectoryPermission = 0755 +const defaultDirectoryPermission = 0o755 // Install performs an installation operation. type Install struct { @@ -718,7 +718,7 @@ func (i *Install) replaceRelease(rel *release.Release) error { // write the to /. controls if the file is created or content will be appended func writeToFile(outputDir string, name string, data string, appendData bool) error { - outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) + outfileName := outputDir + string(filepath.Separator) + name err := ensureDirectoryForFile(outfileName) if err != nil { @@ -744,7 +744,7 @@ func writeToFile(outputDir string, name string, data string, appendData bool) er func createOrOpenFile(filename string, appendData bool) (*os.File, error) { if appendData { - return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) + return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0o600) } return os.Create(filename) } @@ -976,7 +976,7 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) ( dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password)) } - if err := os.MkdirAll(settings.RepositoryCache, 0755); err != nil { + if err := os.MkdirAll(settings.RepositoryCache, 0o755); err != nil { return "", err } diff --git a/pkg/action/package.go b/pkg/action/package.go index 86426b412..002203f40 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -194,7 +194,7 @@ func (p *Package) Clearsign(filename string) error { return err } - return os.WriteFile(filename+".prov", []byte(sig), 0644) + return os.WriteFile(filename+".prov", []byte(sig), 0o644) } // promptUser implements provenance.PassphraseFetcher diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go index f8a55c0e0..ccba4f847 100644 --- a/pkg/action/package_test.go +++ b/pkg/action/package_test.go @@ -92,7 +92,7 @@ func TestPassphraseFileFetcher_WithStdinAndMultipleFetches(t *testing.T) { passphrase := "secret-from-stdin" go func() { - _, err = w.Write([]byte(passphrase + "\n")) + _, err = w.WriteString(passphrase + "\n") require.NoError(t, err) }() diff --git a/pkg/action/pull.go b/pkg/action/pull.go index dd051167b..168011d40 100644 --- a/pkg/action/pull.go +++ b/pkg/action/pull.go @@ -162,7 +162,7 @@ func (p *Pull) Run(chartRef string) (string, error) { } if _, err := os.Stat(udCheck); err != nil { - if err := os.MkdirAll(udCheck, 0755); err != nil { + if err := os.MkdirAll(udCheck, 0o755); err != nil { return out.String(), fmt.Errorf("failed to untar (mkdir): %w", err) } } else { diff --git a/pkg/chart/loader/load_test.go b/pkg/chart/loader/load_test.go index 40f46c09b..a42861eac 100644 --- a/pkg/chart/loader/load_test.go +++ b/pkg/chart/loader/load_test.go @@ -55,7 +55,7 @@ description: A test chart for name, data := range files { header := &tar.Header{ Name: filepath.Join(chartName, name), - Mode: 0644, + Mode: 0o644, Size: int64(len(data)), ModTime: time.Now(), } diff --git a/pkg/chart/v2/lint/rules/crds_test.go b/pkg/chart/v2/lint/rules/crds_test.go index 759bb54fa..7016c4d9a 100644 --- a/pkg/chart/v2/lint/rules/crds_test.go +++ b/pkg/chart/v2/lint/rules/crds_test.go @@ -45,11 +45,11 @@ func TestCrdWithEmptyDocument(t *testing.T) { `apiVersion: v1 name: test version: 0.1.0 -`), 0644) +`), 0o644) // CRD with comments before --- (creates empty document) crdsDir := filepath.Join(chartDir, "crds") - os.Mkdir(crdsDir, 0755) + os.Mkdir(crdsDir, 0o755) os.WriteFile(filepath.Join(crdsDir, "test.yaml"), []byte( `# Comments create empty document --- @@ -57,7 +57,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: test.example.io -`), 0644) +`), 0o644) linter := support.Linter{ChartDir: chartDir} Crds(&linter) diff --git a/pkg/chart/v2/lint/rules/values_test.go b/pkg/chart/v2/lint/rules/values_test.go index 54c7e6457..c0d2f5299 100644 --- a/pkg/chart/v2/lint/rules/values_test.go +++ b/pkg/chart/v2/lint/rules/values_test.go @@ -176,7 +176,7 @@ func TestValidateValuesFile(t *testing.T) { func createTestingSchema(t *testing.T, dir string) string { t.Helper() schemafile := filepath.Join(dir, "values.schema.json") - if err := os.WriteFile(schemafile, []byte(testSchema), 0700); err != nil { + if err := os.WriteFile(schemafile, []byte(testSchema), 0o700); err != nil { t.Fatalf("Failed to write schema to tmpdir: %s", err) } return schemafile diff --git a/pkg/chart/v2/loader/directory.go b/pkg/chart/v2/loader/directory.go index 82578d924..a609ecbbf 100644 --- a/pkg/chart/v2/loader/directory.go +++ b/pkg/chart/v2/loader/directory.go @@ -114,7 +114,7 @@ func LoadDir(dir string) (*chart.Chart, error) { files = append(files, &archive.BufferedFile{Name: n, ModTime: fi.ModTime(), Data: data}) return nil } - if err = sympath.Walk(topdir, walk); err != nil { + if err := sympath.Walk(topdir, walk); err != nil { return c, err } diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index be7041386..e422c30de 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -430,7 +430,7 @@ func TestLoadInvalidArchive(t *testing.T) { h := &tar.Header{ Name: internalPath, - Mode: 0755, + Mode: 0o755, Size: int64(len(body)), ModTime: time.Now(), } diff --git a/pkg/chart/v2/util/chartfile.go b/pkg/chart/v2/util/chartfile.go index 1f9c712b2..525f1efd4 100644 --- a/pkg/chart/v2/util/chartfile.go +++ b/pkg/chart/v2/util/chartfile.go @@ -67,7 +67,7 @@ func SaveChartfile(filename string, cf *chart.Metadata) error { if err != nil { return err } - return os.WriteFile(filename, out, 0644) + return os.WriteFile(filename, out, 0o644) } // IsChartDir validate a chart directory. diff --git a/pkg/chart/v2/util/create.go b/pkg/chart/v2/util/create.go index 36e3bfe72..82e32d90d 100644 --- a/pkg/chart/v2/util/create.go +++ b/pkg/chart/v2/util/create.go @@ -802,7 +802,7 @@ func Create(name, dir string) (string, error) { } } // Need to add the ChartsDir explicitly as it does not contain any file OOTB - if err := os.MkdirAll(filepath.Join(cdir, ChartsDir), 0755); err != nil { + if err := os.MkdirAll(filepath.Join(cdir, ChartsDir), 0o755); err != nil { return cdir, err } return cdir, nil @@ -815,10 +815,10 @@ func transform(src, replacement string) []byte { } func writeFile(name string, content []byte) error { - if err := os.MkdirAll(filepath.Dir(name), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(name), 0o755); err != nil { return err } - return os.WriteFile(name, content, 0644) + return os.WriteFile(name, content, 0o644) } func validateChartName(name string) error { diff --git a/pkg/chart/v2/util/expand.go b/pkg/chart/v2/util/expand.go index 6022d5869..e33cff2d7 100644 --- a/pkg/chart/v2/util/expand.go +++ b/pkg/chart/v2/util/expand.go @@ -88,11 +88,11 @@ func Expand(dir string, r io.Reader) error { // Make sure the necessary subdirs get created. basedir := filepath.Dir(outpath) - if err := os.MkdirAll(basedir, 0755); err != nil { + if err := os.MkdirAll(basedir, 0o755); err != nil { return err } - if err := os.WriteFile(outpath, file.Data, 0644); err != nil { + if err := os.WriteFile(outpath, file.Data, 0o644); err != nil { return err } } diff --git a/pkg/chart/v2/util/save.go b/pkg/chart/v2/util/save.go index e66d86991..d8189c8bf 100644 --- a/pkg/chart/v2/util/save.go +++ b/pkg/chart/v2/util/save.go @@ -49,7 +49,7 @@ func SaveDir(c *chart.Chart, dest string) error { if fi, err := os.Stat(outdir); err == nil && !fi.IsDir() { return fmt.Errorf("file %s already exists and is not a directory", outdir) } - if err := os.MkdirAll(outdir, 0755); err != nil { + if err := os.MkdirAll(outdir, 0o755); err != nil { return err } @@ -115,7 +115,7 @@ func Save(c *chart.Chart, outDir string) (string, error) { dir := filepath.Dir(filename) if stat, err := os.Stat(dir); err != nil { if errors.Is(err, fs.ErrNotExist) { - if err2 := os.MkdirAll(dir, 0755); err2 != nil { + if err2 := os.MkdirAll(dir, 0o755); err2 != nil { return "", err2 } } else { @@ -242,7 +242,7 @@ func writeToTar(out *tar.Writer, name string, body []byte, modTime time.Time) er // TODO: Do we need to create dummy parent directory names if none exist? h := &tar.Header{ Name: filepath.ToSlash(name), - Mode: 0644, + Mode: 0o644, Size: int64(len(body)), ModTime: modTime, } diff --git a/pkg/chart/v2/util/save_test.go b/pkg/chart/v2/util/save_test.go index 2f2b73efd..4870378d6 100644 --- a/pkg/chart/v2/util/save_test.go +++ b/pkg/chart/v2/util/save_test.go @@ -262,7 +262,7 @@ func TestSaveDir(t *testing.T) { tmp2 := t.TempDir() c.Metadata.Name = "../ahab" pth := filepath.Join(tmp2, "tmpcharts") - if err := os.MkdirAll(filepath.Join(pth), 0755); err != nil { + if err := os.MkdirAll(filepath.Join(pth), 0o755); err != nil { t.Fatal(err) } diff --git a/pkg/cli/values/options_test.go b/pkg/cli/values/options_test.go index f6a116f18..498f2d3ba 100644 --- a/pkg/cli/values/options_test.go +++ b/pkg/cli/values/options_test.go @@ -91,7 +91,7 @@ func TestReadFile(t *testing.T) { tmpDir := t.TempDir() filePath := filepath.Join(tmpDir, "test.txt") content := []byte("local file content") - require.NoError(t, os.WriteFile(filePath, content, 0644)) + require.NoError(t, os.WriteFile(filePath, content, 0o644)) return filePath, func() {} // cleanup handled by t.TempDir() }, expectError: false, @@ -152,7 +152,7 @@ func TestReadFile(t *testing.T) { fileName := "ftp_file.txt" // Valid filename for filesystem filePath := filepath.Join(tmpDir, fileName) content := []byte("local fallback content") - require.NoError(t, os.WriteFile(filePath, content, 0644)) + require.NoError(t, os.WriteFile(filePath, content, 0o644)) return filePath, func() {} }, expectError: false, diff --git a/pkg/cmd/docs.go b/pkg/cmd/docs.go index 7fae60743..6463170b9 100644 --- a/pkg/cmd/docs.go +++ b/pkg/cmd/docs.go @@ -86,7 +86,7 @@ func (o *docsOptions) run(_ io.Writer) error { base := filepath.Base(filename) name := strings.TrimSuffix(base, path.Ext(base)) title := cases.Title(language.Und, cases.NoLower).String(strings.ReplaceAll(name, "_", " ")) - return fmt.Sprintf("---\ntitle: \"%s\"\n---\n\n", title) + return fmt.Sprintf("---\ntitle: %q\n---\n\n", title) } return doc.GenMarkdownTreeCustom(o.topCmd, o.dest, hdrFunc, standardLinks) diff --git a/pkg/cmd/load_plugins.go b/pkg/cmd/load_plugins.go index b6cc38ce6..474eaea2e 100644 --- a/pkg/cmd/load_plugins.go +++ b/pkg/cmd/load_plugins.go @@ -220,8 +220,7 @@ type pluginCommand struct { // and add the dynamic completion hook to call the optional plugin.complete func loadCompletionForPlugin(pluginCmd *cobra.Command, plug plugin.Plugin) { // Parse the yaml file providing the plugin's sub-commands and flags - cmds, err := loadFile(strings.Join( - []string{plug.Dir(), pluginStaticCompletionFile}, string(filepath.Separator))) + cmds, err := loadFile(plug.Dir() + string(filepath.Separator) + pluginStaticCompletionFile) if err != nil { // The file could be missing or invalid. No static completion for this plugin. @@ -351,7 +350,7 @@ func pluginDynamicComp(plug plugin.Plugin, cmd *cobra.Command, args []string, to } // We will call the dynamic completion script of the plugin - main := strings.Join([]string{plug.Dir(), pluginDynamicCompletionExecutable}, string(filepath.Separator)) + main := plug.Dir() + string(filepath.Separator) + pluginDynamicCompletionExecutable // We must include all sub-commands passed on the command-line. // To do that, we pass-in the entire CommandPath, except the first two elements diff --git a/pkg/cmd/plugin_package.go b/pkg/cmd/plugin_package.go index d82f4d2cc..019ae6d4a 100644 --- a/pkg/cmd/plugin_package.go +++ b/pkg/cmd/plugin_package.go @@ -91,7 +91,7 @@ func (o *pluginPackageOptions) run(out io.Writer) error { } // Create destination directory if needed - if err := os.MkdirAll(o.destination, 0755); err != nil { + if err := os.MkdirAll(o.destination, 0o755); err != nil { return err } @@ -158,7 +158,7 @@ func (o *pluginPackageOptions) run(out io.Writer) error { // Write the signature provFile := tarballPath + ".prov" - if err := os.WriteFile(provFile, []byte(sig), 0644); err != nil { + if err := os.WriteFile(provFile, []byte(sig), 0o644); err != nil { os.Remove(tarballPath) return err } diff --git a/pkg/cmd/plugin_package_test.go b/pkg/cmd/plugin_package_test.go index 7d97562f8..b5ecf2bf1 100644 --- a/pkg/cmd/plugin_package_test.go +++ b/pkg/cmd/plugin_package_test.go @@ -43,12 +43,12 @@ func TestPluginPackageWithoutSigning(t *testing.T) { // Create a test plugin directory tempDir := t.TempDir() pluginDir := filepath.Join(tempDir, "test-plugin") - if err := os.MkdirAll(pluginDir, 0755); err != nil { + if err := os.MkdirAll(pluginDir, 0o755); err != nil { t.Fatal(err) } // Create a plugin.yaml file - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0644); err != nil { + if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644); err != nil { t.Fatal(err) } @@ -94,12 +94,12 @@ func TestPluginPackageDefaultRequiresSigning(t *testing.T) { // Create a test plugin directory tempDir := t.TempDir() pluginDir := filepath.Join(tempDir, "test-plugin") - if err := os.MkdirAll(pluginDir, 0755); err != nil { + if err := os.MkdirAll(pluginDir, 0o755); err != nil { t.Fatal(err) } // Create a plugin.yaml file - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0644); err != nil { + if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644); err != nil { t.Fatal(err) } @@ -131,12 +131,12 @@ func TestPluginPackageSigningFailure(t *testing.T) { // Create a test plugin directory tempDir := t.TempDir() pluginDir := filepath.Join(tempDir, "test-plugin") - if err := os.MkdirAll(pluginDir, 0755); err != nil { + if err := os.MkdirAll(pluginDir, 0o755); err != nil { t.Fatal(err) } // Create a plugin.yaml file - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0644); err != nil { + if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644); err != nil { t.Fatal(err) } diff --git a/pkg/cmd/plugin_uninstall_test.go b/pkg/cmd/plugin_uninstall_test.go index 93d4dc8a8..f715b09c8 100644 --- a/pkg/cmd/plugin_uninstall_test.go +++ b/pkg/cmd/plugin_uninstall_test.go @@ -39,7 +39,7 @@ func TestPluginUninstallCleansUpVersionedFiles(t *testing.T) { // Create plugin directory pluginDir := filepath.Join(pluginsDir, pluginName) - if err := os.MkdirAll(pluginDir, 0755); err != nil { + if err := os.MkdirAll(pluginDir, 0o755); err != nil { t.Fatal(err) } @@ -49,7 +49,7 @@ version: 1.2.3 description: Test plugin command: $HELM_PLUGIN_DIR/test-plugin ` - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginYAML), 0644); err != nil { + if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginYAML), 0o644); err != nil { t.Fatal(err) } @@ -58,14 +58,14 @@ command: $HELM_PLUGIN_DIR/test-plugin provFile := filepath.Join(pluginsDir, "test-plugin-1.2.3.tgz.prov") otherVersionTarball := filepath.Join(pluginsDir, "test-plugin-2.0.0.tgz") - if err := os.WriteFile(tarballFile, []byte("fake tarball"), 0644); err != nil { + if err := os.WriteFile(tarballFile, []byte("fake tarball"), 0o644); err != nil { t.Fatal(err) } - if err := os.WriteFile(provFile, []byte("fake provenance"), 0644); err != nil { + if err := os.WriteFile(provFile, []byte("fake provenance"), 0o644); err != nil { t.Fatal(err) } // Create another version that should NOT be removed - if err := os.WriteFile(otherVersionTarball, []byte("other version"), 0644); err != nil { + if err := os.WriteFile(otherVersionTarball, []byte("other version"), 0o644); err != nil { t.Fatal(err) } diff --git a/pkg/cmd/plugin_verify_test.go b/pkg/cmd/plugin_verify_test.go index e631814dd..4ce664c0c 100644 --- a/pkg/cmd/plugin_verify_test.go +++ b/pkg/cmd/plugin_verify_test.go @@ -102,7 +102,7 @@ func TestPluginVerifyCmd_InvalidProvenance(t *testing.T) { // Create invalid .prov file provFile := pluginTgz + ".prov" - if err := os.WriteFile(provFile, []byte("invalid provenance"), 0644); err != nil { + if err := os.WriteFile(provFile, []byte("invalid provenance"), 0o644); err != nil { t.Fatal(err) } defer os.Remove(provFile) @@ -178,12 +178,12 @@ func createTestPluginDir(t *testing.T) string { // Create temporary directory with plugin structure tmpDir := t.TempDir() pluginDir := filepath.Join(tmpDir, "test-plugin") - if err := os.MkdirAll(pluginDir, 0755); err != nil { + if err := os.MkdirAll(pluginDir, 0o755); err != nil { t.Fatalf("Failed to create plugin directory: %v", err) } // Use the same plugin YAML as other cmd tests - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0644); err != nil { + if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644); err != nil { t.Fatalf("Failed to create plugin.yaml: %v", err) } @@ -243,7 +243,7 @@ Version: GnuPG v1 iQEcBAEBCAAGBQJktest... -----END PGP SIGNATURE----- `, hashStr) - if err := os.WriteFile(provFile, []byte(provContent), 0644); err != nil { + if err := os.WriteFile(provFile, []byte(provContent), 0o644); err != nil { t.Fatalf("Failed to create provenance file: %v", err) } } @@ -256,7 +256,7 @@ func createTestKeyring(t *testing.T) string { keyringPath := filepath.Join(tmpDir, "pubring.gpg") // Create empty keyring for testing - if err := os.WriteFile(keyringPath, []byte{}, 0644); err != nil { + if err := os.WriteFile(keyringPath, []byte{}, 0o644); err != nil { t.Fatalf("Failed to create test keyring: %v", err) } diff --git a/pkg/cmd/pull_test.go b/pkg/cmd/pull_test.go index 511061dcd..e1b3e8134 100644 --- a/pkg/cmd/pull_test.go +++ b/pkg/cmd/pull_test.go @@ -229,7 +229,7 @@ func TestPullCmd(t *testing.T) { // Create file or Dir before helm pull --untar, see: https://github.com/helm/helm/issues/7182 if tt.existFile != "" { file := filepath.Join(outdir, tt.existFile) - if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(file), 0o755); err != nil { t.Fatal(err) } _, err := os.Create(file) @@ -239,7 +239,7 @@ func TestPullCmd(t *testing.T) { } if tt.existDir != "" { file := filepath.Join(outdir, tt.existDir) - err := os.MkdirAll(file, 0755) + err := os.MkdirAll(file, 0o755) if err != nil { t.Fatal(err) } @@ -306,7 +306,7 @@ func runPullTests(t *testing.T, tests []struct { } if tt.existDir != "" { file := filepath.Join(outdir, tt.existDir) - err := os.MkdirAll(file, 0755) + err := os.MkdirAll(file, 0o755) if err != nil { t.Fatal(err) } diff --git a/pkg/cmd/repo_add_test.go b/pkg/cmd/repo_add_test.go index 8f8e36d75..1290d3862 100644 --- a/pkg/cmd/repo_add_test.go +++ b/pkg/cmd/repo_add_test.go @@ -270,7 +270,7 @@ func TestRepoAddWithPasswordFromStdin(t *testing.T) { t.Errorf("unexpected error, got '%v'", err) } - if !strings.Contains(result, fmt.Sprintf("\"%s\" has been added to your repositories", testName)) { + if !strings.Contains(result, fmt.Sprintf("%q has been added to your repositories", testName)) { t.Errorf("Repo was not successfully added. Output: %s", result) } } diff --git a/pkg/cmd/repo_remove.go b/pkg/cmd/repo_remove.go index 330e69d3a..1d0bb1099 100644 --- a/pkg/cmd/repo_remove.go +++ b/pkg/cmd/repo_remove.go @@ -68,7 +68,7 @@ func (o *repoRemoveOptions) run(out io.Writer) error { if !r.Remove(name) { return fmt.Errorf("no repo named %q found", name) } - if err := r.WriteFile(o.repoFile, 0600); err != nil { + if err := r.WriteFile(o.repoFile, 0o600); err != nil { return err } diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index bb364231b..ccc1db719 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -239,7 +239,7 @@ func isTestHook(h *release.Hook) bool { // this duplicate code should be removed. It is added here so that the API // surface area is as minimally impacted as possible in fixing the issue. func writeToFile(outputDir string, name string, data string, appendData bool) error { - outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) + outfileName := outputDir + string(filepath.Separator) + name err := ensureDirectoryForFile(outfileName) if err != nil { @@ -265,7 +265,7 @@ func writeToFile(outputDir string, name string, data string, appendData bool) er func createOrOpenFile(filename string, appendData bool) (*os.File, error) { if appendData { - return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) + return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0o600) } return os.Create(filename) } @@ -277,5 +277,5 @@ func ensureDirectoryForFile(file string) error { return err } - return os.MkdirAll(baseDir, 0755) + return os.MkdirAll(baseDir, 0o755) } diff --git a/pkg/downloader/cache.go b/pkg/downloader/cache.go index 92d477e49..539e7a640 100644 --- a/pkg/downloader/cache.go +++ b/pkg/downloader/cache.go @@ -78,11 +78,11 @@ func (c *DiskCache) Get(key [sha256.Size]byte, cacheType string) (string, error) func (c *DiskCache) Put(key [sha256.Size]byte, data io.Reader, cacheType string) (string, error) { // TODO: verify the key and digest of the key are the same. p := c.fileName(key, cacheType) - if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { slog.Error("failed to create cache directory") return p, err } - return p, fileutil.AtomicWriteFile(p, data, 0644) + return p, fileutil.AtomicWriteFile(p, data, 0o644) } // fileName generates the filename in a structured manner where the first part is the diff --git a/pkg/downloader/cache_test.go b/pkg/downloader/cache_test.go index 340c77aba..7d7fb3059 100644 --- a/pkg/downloader/cache_test.go +++ b/pkg/downloader/cache_test.go @@ -105,7 +105,7 @@ func TestDiskCache_PutAndGet(t *testing.T) { t.Run("GetDirectory", func(t *testing.T) { dirKey := sha256.Sum256([]byte("i am a directory")) dirPath := cache.fileName(dirKey, CacheChart) - err := os.MkdirAll(dirPath, 0755) + err := os.MkdirAll(dirPath, 0o755) require.NoError(t, err) _, err = cache.Get(dirKey, CacheChart) diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 22c6c71a3..4a13fc5ed 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -165,7 +165,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven // Use PlatformAtomicWriteFile to handle platform-specific concurrency concerns // (Windows requires locking to avoid "Access Denied" errors when multiple // processes write the same file) - if err := fileutil.PlatformAtomicWriteFile(destfile, data, 0644); err != nil { + if err := fileutil.PlatformAtomicWriteFile(destfile, data, 0o644); err != nil { return destfile, nil, err } @@ -197,7 +197,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven provfile := destfile + ".prov" // Use PlatformAtomicWriteFile for the provenance file as well - if err := fileutil.PlatformAtomicWriteFile(provfile, body, 0644); err != nil { + if err := fileutil.PlatformAtomicWriteFile(provfile, body, 0o644); err != nil { return destfile, nil, err } @@ -321,7 +321,7 @@ func (c *ChartDownloader) DownloadToCache(ref, version string) (string, *provena // Copy chart to a known location with the right name for verification and then // clean it up. tmpdir := filepath.Dir(filepath.Join(c.ContentCache, "tmp")) - if err := os.MkdirAll(tmpdir, 0755); err != nil { + if err := os.MkdirAll(tmpdir, 0o755); err != nil { return pth, ver, err } tmpfile := filepath.Join(tmpdir, name) diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index 8a7514a8e..07b25a1eb 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -417,7 +417,7 @@ func TestDownloadToCache(t *testing.T) { t.Run("download and cache chart", func(t *testing.T) { // Clear cache for this test os.RemoveAll(contentCache) - os.MkdirAll(contentCache, 0755) + os.MkdirAll(contentCache, 0o755) c.Cache = &DiskCache{Root: contentCache} pth, v, err := c.DownloadToCache("test/signtest", "0.1.0") @@ -459,7 +459,7 @@ func TestDownloadToCache(t *testing.T) { t.Run("download and verify", func(t *testing.T) { // Clear cache os.RemoveAll(contentCache) - os.MkdirAll(contentCache, 0755) + os.MkdirAll(contentCache, 0o755) c.Cache = &DiskCache{Root: contentCache} c.Verify = VerifyAlways c.Keyring = "testdata/helm-test-key.pub" diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index ac4f2207e..543f685eb 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -257,7 +257,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { return fmt.Errorf("%q is not a directory", destPath) } } else if errors.Is(err, stdfs.ErrNotExist) { - if err := os.MkdirAll(destPath, 0755); err != nil { + if err := os.MkdirAll(destPath, 0o755); err != nil { return err } } else { @@ -265,7 +265,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { } // Prepare tmpPath - if err := os.MkdirAll(tmpPath, 0755); err != nil { + if err := os.MkdirAll(tmpPath, 0o755); err != nil { return err } defer os.RemoveAll(tmpPath) @@ -378,7 +378,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { } func parseOCIRef(chartRef string) (string, string, error) { - refTagRegexp := regexp.MustCompile(`^(oci://[^:]+(:[0-9]{1,5})?[^:]+):(.*)$`) + refTagRegexp := regexp.MustCompile(`^(oci://[^:]+(:\d{1,5})?[^:]+):(.*)$`) caps := refTagRegexp.FindStringSubmatch(chartRef) if len(caps) != 4 { return "", "", fmt.Errorf("improperly formatted oci chart reference: %s", chartRef) @@ -866,7 +866,7 @@ func writeLock(chartpath string, lock *chart.Lock, legacyLockfile bool) error { } } - return os.WriteFile(dest, data, 0644) + return os.WriteFile(dest, data, 0o644) } // archive a dep chart from local directory and save it into destPath diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index 9e27f183f..aea64a0b5 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -264,7 +264,7 @@ func TestDownloadAll(t *testing.T) { } // create a 'tmpcharts' directory to test #5567 - if err := os.MkdirAll(filepath.Join(chartPath, "tmpcharts"), 0755); err != nil { + if err := os.MkdirAll(filepath.Join(chartPath, "tmpcharts"), 0o755); err != nil { t.Fatal(err) } if err := m.downloadAll([]*chart.Dependency{signDep, localDep}); err != nil { @@ -282,10 +282,10 @@ func TestDownloadAll(t *testing.T) { description: A Helm chart for Kubernetes name: ../bad-local-subchart version: 0.1.0` - if err := os.MkdirAll(filepath.Join(chartPath, "testdata", "bad-local-subchart"), 0755); err != nil { + if err := os.MkdirAll(filepath.Join(chartPath, "testdata", "bad-local-subchart"), 0o755); err != nil { t.Fatal(err) } - err = os.WriteFile(filepath.Join(chartPath, "testdata", "bad-local-subchart", "Chart.yaml"), []byte(badchartyaml), 0644) + err = os.WriteFile(filepath.Join(chartPath, "testdata", "bad-local-subchart", "Chart.yaml"), []byte(badchartyaml), 0o644) if err != nil { t.Fatal(err) } @@ -735,7 +735,7 @@ func TestWriteLock(t *testing.T) { t.Run("overwrite existing lock file", func(t *testing.T) { dir := t.TempDir() lockfilePath := filepath.Join(dir, "Chart.lock") - assert.NoError(t, os.WriteFile(lockfilePath, []byte("old content"), 0644)) + assert.NoError(t, os.WriteFile(lockfilePath, []byte("old content"), 0o644)) err = writeLock(dir, lock, false) assert.NoError(t, err) @@ -748,7 +748,7 @@ func TestWriteLock(t *testing.T) { t.Run("lock file is a symlink", func(t *testing.T) { dir := t.TempDir() dummyFile := filepath.Join(dir, "dummy.txt") - assert.NoError(t, os.WriteFile(dummyFile, []byte("dummy"), 0644)) + assert.NoError(t, os.WriteFile(dummyFile, []byte("dummy"), 0o644)) lockfilePath := filepath.Join(dir, "Chart.lock") assert.NoError(t, os.Symlink(dummyFile, lockfilePath)) @@ -761,7 +761,7 @@ func TestWriteLock(t *testing.T) { t.Run("chart path is not a directory", func(t *testing.T) { dir := t.TempDir() filePath := filepath.Join(dir, "not-a-dir") - assert.NoError(t, os.WriteFile(filePath, []byte("file"), 0644)) + assert.NoError(t, os.WriteFile(filePath, []byte("file"), 0o644)) err = writeLock(filePath, lock, false) assert.Error(t, err) diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 4f7fa4590..d304aea7d 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -539,7 +539,7 @@ func (p byPathLen) Less(i, j int) bool { a, b := p[i], p[j] ca, cb := strings.Count(a, "/"), strings.Count(b, "/") if ca == cb { - return strings.Compare(a, b) == -1 + return a < b } return ca < cb } diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index f3116d626..57bd255b3 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -569,7 +569,7 @@ func TestHttpClientInsecureSkipVerify(t *testing.T) { g.opts.keyFile = "testdata/client.key" g.opts.insecureSkipVerifyTLS = true transport := verifyInsecureSkipVerify(t, &g, "HTTPGetter with 2 way ssl", true) - if len(transport.TLSClientConfig.Certificates) <= 0 { + if len(transport.TLSClientConfig.Certificates) == 0 { t.Fatal("transport.TLSClientConfig.Certificates is not present") } } diff --git a/pkg/pusher/ocipusher_test.go b/pkg/pusher/ocipusher_test.go index b7d362681..52536dfd2 100644 --- a/pkg/pusher/ocipusher_test.go +++ b/pkg/pusher/ocipusher_test.go @@ -301,12 +301,12 @@ func TestOCIPusher_Push_ChartOperations(t *testing.T) { dst.Close() // Make the file unreadable - if err := os.Chmod(tempChart, 0000); err != nil { + if err := os.Chmod(tempChart, 0o000); err != nil { t.Fatal(err) } return tempChart, func() { - os.Chmod(tempChart, 0644) // Restore permissions for cleanup + os.Chmod(tempChart, 0o644) // Restore permissions for cleanup } }, href: "oci://localhost:5000/test", @@ -342,7 +342,7 @@ func TestOCIPusher_Push_ChartOperations(t *testing.T) { dst.Close() // Create provenance file - if err := os.WriteFile(tempProv, []byte("test provenance data"), 0644); err != nil { + if err := os.WriteFile(tempProv, []byte("test provenance data"), 0o644); err != nil { t.Fatal(err) } diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index 7d7b5a112..a1cf11310 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -36,7 +36,6 @@ import ( _ "github.com/distribution/distribution/v3/registry/auth/token" _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "golang.org/x/crypto/bcrypt" @@ -75,9 +74,9 @@ type TestRegistry struct { func setup(suite *TestRegistry, tlsEnabled, insecure bool, auth string) { suite.WorkspaceDir = testWorkspaceDir err := os.RemoveAll(suite.WorkspaceDir) - require.NoError(suite.T(), err, "no error removing test workspace dir") - err = os.Mkdir(suite.WorkspaceDir, 0700) - require.NoError(suite.T(), err, "no error creating test workspace dir") + suite.Require().NoError(err, "no error removing test workspace dir") + err = os.Mkdir(suite.WorkspaceDir, 0o700) + suite.Require().NoError(err, "no error creating test workspace dir") var out bytes.Buffer @@ -123,7 +122,7 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool, auth string) { pwBytes, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost) suite.Require().NoError(err, "no error generating bcrypt password for test htpasswd file") htpasswdPath := filepath.Join(suite.WorkspaceDir, testHtpasswdFileBasename) - err = os.WriteFile(htpasswdPath, fmt.Appendf(nil, "%s:%s\n", testUsername, string(pwBytes)), 0644) + err = os.WriteFile(htpasswdPath, fmt.Appendf(nil, "%s:%s\n", testUsername, string(pwBytes)), 0o644) suite.Require().NoError(err, "no error creating test htpasswd file") // Registry config diff --git a/pkg/repo/v1/chartrepo.go b/pkg/repo/v1/chartrepo.go index 09b74d602..c7c4a3e7b 100644 --- a/pkg/repo/v1/chartrepo.go +++ b/pkg/repo/v1/chartrepo.go @@ -109,14 +109,14 @@ func (r *ChartRepository) DownloadIndexFile() (string, error) { fmt.Fprintln(&charts, name) } chartsFile := filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)) - os.MkdirAll(filepath.Dir(chartsFile), 0755) + os.MkdirAll(filepath.Dir(chartsFile), 0o755) - fileutil.AtomicWriteFile(chartsFile, bytes.NewReader([]byte(charts.String())), 0644) + fileutil.AtomicWriteFile(chartsFile, bytes.NewReader([]byte(charts.String())), 0o644) // Create the index file in the cache directory fname := filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name)) - os.MkdirAll(filepath.Dir(fname), 0755) - return fname, fileutil.AtomicWriteFile(fname, bytes.NewReader(index), 0644) + os.MkdirAll(filepath.Dir(fname), 0o755) + return fname, fileutil.AtomicWriteFile(fname, bytes.NewReader(index), 0o644) } type findChartInRepoURLOptions struct { diff --git a/pkg/repo/v1/index_test.go b/pkg/repo/v1/index_test.go index 9259e5a4d..126be125f 100644 --- a/pkg/repo/v1/index_test.go +++ b/pkg/repo/v1/index_test.go @@ -548,7 +548,7 @@ func TestIndexWrite(t *testing.T) { } dir := t.TempDir() testpath := filepath.Join(dir, "test") - i.WriteFile(testpath, 0600) + i.WriteFile(testpath, 0o600) got, err := os.ReadFile(testpath) if err != nil { @@ -566,7 +566,7 @@ func TestIndexJSONWrite(t *testing.T) { } dir := t.TempDir() testpath := filepath.Join(dir, "test") - i.WriteJSONFile(testpath, 0600) + i.WriteJSONFile(testpath, 0o600) got, err := os.ReadFile(testpath) if err != nil { diff --git a/pkg/repo/v1/repo.go b/pkg/repo/v1/repo.go index 38d2b0ca1..199b56ed8 100644 --- a/pkg/repo/v1/repo.go +++ b/pkg/repo/v1/repo.go @@ -118,7 +118,7 @@ func (r *File) WriteFile(path string, perm os.FileMode) error { if err != nil { return err } - if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return err } return os.WriteFile(path, data, perm) diff --git a/pkg/repo/v1/repo_test.go b/pkg/repo/v1/repo_test.go index 9b5c54309..960635fc5 100644 --- a/pkg/repo/v1/repo_test.go +++ b/pkg/repo/v1/repo_test.go @@ -202,7 +202,7 @@ func TestWriteFile(t *testing.T) { t.Errorf("failed to create test-file (%v)", err) } defer os.Remove(file.Name()) - if err := sampleRepository.WriteFile(file.Name(), 0600); err != nil { + if err := sampleRepository.WriteFile(file.Name(), 0o600); err != nil { t.Errorf("failed to write file (%v)", err) } diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go index 00a0832b3..2636755a9 100644 --- a/pkg/storage/driver/cfgmaps.go +++ b/pkg/storage/driver/cfgmaps.go @@ -237,7 +237,7 @@ func (cfgmaps *ConfigMaps) Delete(key string) (rls release.Releaser, err error) return nil, err } // delete the release - if err = cfgmaps.impl.Delete(context.Background(), key, metav1.DeleteOptions{}); err != nil { + if err := cfgmaps.impl.Delete(context.Background(), key, metav1.DeleteOptions{}); err != nil { return rls, err } return rls, nil diff --git a/pkg/storage/driver/util.go b/pkg/storage/driver/util.go index 756731ef6..4bc48db23 100644 --- a/pkg/storage/driver/util.go +++ b/pkg/storage/driver/util.go @@ -49,7 +49,7 @@ func encodeRelease(rls *rspb.Release) (string, error) { w.Close() return "", err } - if err = w.Close(); err != nil { + if err := w.Close(); err != nil { return "", err } diff --git a/pkg/strvals/literal_parser_test.go b/pkg/strvals/literal_parser_test.go index d7892844f..5d65557c5 100644 --- a/pkg/strvals/literal_parser_test.go +++ b/pkg/strvals/literal_parser_test.go @@ -16,6 +16,7 @@ limitations under the License. package strvals import ( + "bytes" "fmt" "strings" "testing" @@ -298,7 +299,7 @@ func TestParseLiteral(t *testing.T) { require.NoError(t, err, "Error serializing parsed value") } - if string(y1) != string(y2) { + if !bytes.Equal(y1, y2) { assert.Equal(t, string(y1), string(y2), tt.str) } } @@ -409,7 +410,7 @@ func TestParseLiteralInto(t *testing.T) { require.NoError(t, err, "Error serializing parsed value") } - if string(y1) != string(y2) { + if !bytes.Equal(y1, y2) { assert.Equal(t, string(y1), string(y2), tt.input) } } @@ -471,7 +472,7 @@ func TestParseLiteralNestedLevels(t *testing.T) { require.NoError(t, err, "Error serializing parsed value") } - if string(y1) != string(y2) { + if !bytes.Equal(y1, y2) { assert.Equal(t, string(y1), string(y2), tt.str) } } diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index 1dbbd0e1b..a0b72bee3 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -226,7 +226,7 @@ func (t *parser) key(data map[string]any, nestedNameLevel int) (reterr error) { // discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset). var jsonval any dec := json.NewDecoder(strings.NewReader(t.sc.String())) - if err = dec.Decode(&jsonval); err != nil { + if err := dec.Decode(&jsonval); err != nil { return err } set(data, string(k), jsonval) @@ -358,7 +358,7 @@ func (t *parser) listItem(list []any, i, nestedNameLevel int) ([]any, error) { // discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset). var jsonval any dec := json.NewDecoder(strings.NewReader(t.sc.String())) - if err = dec.Decode(&jsonval); err != nil { + if err := dec.Decode(&jsonval); err != nil { return list, err } if list, err = setIndex(list, i, jsonval); err != nil { diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go index 1a9559236..6d254ca8d 100644 --- a/pkg/strvals/parser_test.go +++ b/pkg/strvals/parser_test.go @@ -16,6 +16,7 @@ limitations under the License. package strvals import ( + "bytes" "fmt" "strings" "testing" @@ -400,7 +401,7 @@ func TestParseSet(t *testing.T) { require.NoError(t, err, "Error serializing parsed value") } - if string(y1) != string(y2) { + if !bytes.Equal(y1, y2) { assert.Equal(t, string(y1), string(y2), tt.str) } } @@ -423,7 +424,7 @@ func TestParseSet(t *testing.T) { require.NoError(t, err, "Error serializing parsed value") } - if string(y1) != string(y2) { + if !bytes.Equal(y1, y2) { assert.Equal(t, string(y1), string(y2), tt.str) } } @@ -534,7 +535,7 @@ func TestParseInto(t *testing.T) { require.NoError(t, err, "Error serializing parsed value") } - if string(y1) != string(y2) { + if !bytes.Equal(y1, y2) { assert.Equal(t, string(y1), string(y2), tt.input) } } @@ -567,7 +568,7 @@ func TestParseIntoString(t *testing.T) { require.NoError(t, err, "Error serializing parsed value") } - if string(y1) != string(y2) { + if !bytes.Equal(y1, y2) { assert.Equal(t, string(y1), string(y2), input) } } @@ -667,7 +668,7 @@ func TestParseJSON(t *testing.T) { require.NoError(t, err, "Error serializing parsed value") } - if string(y1) != string(y2) { + if !bytes.Equal(y1, y2) { assert.Equal(t, string(y1), string(y2), tt.input) } } @@ -697,7 +698,7 @@ func TestParseFile(t *testing.T) { require.NoError(t, err, "Error serializing parsed value") } - if string(y1) != string(y2) { + if !bytes.Equal(y1, y2) { assert.Equal(t, string(y1), string(y2), input) } } @@ -728,7 +729,7 @@ func TestParseIntoFile(t *testing.T) { require.NoError(t, err, "Error serializing parsed value") } - if string(y1) != string(y2) { + if !bytes.Equal(y1, y2) { assert.Equal(t, string(y1), string(y2), input) } } @@ -797,7 +798,7 @@ func TestParseSetNestedLevels(t *testing.T) { require.NoError(t, err, "Error serializing parsed value") } - if string(y1) != string(y2) { + if !bytes.Equal(y1, y2) { assert.Equal(t, string(y1), string(y2), tt.str) } } From 9b30076180b8f73bbe25d0f4cd752207154ad0c1 Mon Sep 17 00:00:00 2001 From: LarytheLord Date: Thu, 5 Mar 2026 23:47:34 +0530 Subject: [PATCH 401/571] test(cli): isolate user-agent rest config test from host kubeconfig Signed-off-by: LarytheLord --- pkg/cli/environment_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go index 9303389a3..df87b86df 100644 --- a/pkg/cli/environment_test.go +++ b/pkg/cli/environment_test.go @@ -18,6 +18,7 @@ package cli import ( "os" + "path/filepath" "strings" "testing" @@ -220,6 +221,30 @@ func TestEnvOrBool(t *testing.T) { func TestUserAgentHeaderInK8sRESTClientConfig(t *testing.T) { defer resetEnv()() + kubeconfigPath := filepath.Join(t.TempDir(), "config") + kubeconfig := `apiVersion: v1 +clusters: +- cluster: + server: https://127.0.0.1:6443 + name: test +contexts: +- context: + cluster: test + user: test-user + name: test +current-context: test +kind: Config +preferences: {} +users: +- name: test-user + user: + token: test-token +` + if err := os.WriteFile(kubeconfigPath, []byte(kubeconfig), 0o600); err != nil { + t.Fatalf("failed to create test kubeconfig: %v", err) + } + t.Setenv("KUBECONFIG", kubeconfigPath) + settings := New() restConfig, err := settings.RESTClientGetter().ToRESTConfig() require.NoError(t, err) From 47bde119597961113d769a9d46a565f448565e7a Mon Sep 17 00:00:00 2001 From: LarytheLord Date: Sun, 8 Mar 2026 18:32:35 +0530 Subject: [PATCH 402/571] test(cli): use t.Cleanup for resetEnv with t.Setenv Signed-off-by: LarytheLord --- pkg/cli/environment_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go index df87b86df..2d7851a4f 100644 --- a/pkg/cli/environment_test.go +++ b/pkg/cli/environment_test.go @@ -120,7 +120,8 @@ func TestEnvSettings(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer resetEnv()() + cleanup := resetEnv() + t.Cleanup(cleanup) for k, v := range tt.envvars { t.Setenv(k, v) @@ -219,7 +220,8 @@ func TestEnvOrBool(t *testing.T) { } func TestUserAgentHeaderInK8sRESTClientConfig(t *testing.T) { - defer resetEnv()() + cleanup := resetEnv() + t.Cleanup(cleanup) kubeconfigPath := filepath.Join(t.TempDir(), "config") kubeconfig := `apiVersion: v1 From f34ad6c337a53f0d77d1e24b5d1c6cdd1c57bc63 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 6 Jul 2026 07:57:58 +0000 Subject: [PATCH 403/571] chore: fix several checks issues from testifylint Signed-off-by: Matthieu MOREL --- .golangci.yml | 10 - internal/chart/v3/lint/lint_test.go | 3 +- internal/fileutil/fileutil_test.go | 6 +- internal/logging/logging_test.go | 5 +- internal/plugin/loader_test.go | 2 +- internal/plugin/metadata_v1_test.go | 3 +- internal/plugin/runtime_extismv1_test.go | 2 +- .../statusreaders/job_status_reader_test.go | 5 +- .../statusreaders/pod_status_reader_test.go | 5 +- internal/tlsutil/tls_test.go | 7 +- internal/urlutil/urlutil_test.go | 3 +- pkg/action/action_test.go | 40 ++-- pkg/action/dependency_test.go | 4 +- pkg/action/get_metadata_test.go | 8 +- pkg/action/get_values_test.go | 4 +- pkg/action/hooks_test.go | 14 +- pkg/action/install_test.go | 219 ++++++++++-------- pkg/action/list_test.go | 67 +++--- pkg/action/package_test.go | 5 +- pkg/action/pull_test.go | 4 +- pkg/action/registry_login_test.go | 11 +- pkg/action/release_testing_test.go | 3 +- pkg/action/rollback_test.go | 3 +- pkg/action/uninstall_test.go | 33 +-- pkg/action/upgrade_test.go | 119 +++++----- pkg/action/validate_test.go | 43 ++-- pkg/chart/common/util/coalesce_test.go | 16 +- pkg/chart/v2/lint/lint_test.go | 3 +- pkg/cli/values/options_test.go | 2 +- pkg/cmd/helpers_test.go | 4 +- pkg/cmd/history_test.go | 2 +- pkg/downloader/cache_test.go | 2 +- pkg/downloader/manager_test.go | 35 +-- pkg/engine/funcs_test.go | 6 +- pkg/kube/client_test.go | 6 +- pkg/kube/statuswait_test.go | 30 +-- pkg/kube/wait_test.go | 5 +- pkg/postrenderer/postrenderer_test.go | 10 +- pkg/registry/chart_test.go | 2 +- pkg/registry/client_scope_test.go | 20 +- pkg/registry/reference_test.go | 15 +- pkg/registry/registry_test.go | 4 +- pkg/release/common_test.go | 12 +- pkg/storage/driver/memory_test.go | 4 +- pkg/storage/storage_test.go | 24 +- 45 files changed, 449 insertions(+), 381 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 518e47052..8c28a6c46 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -139,16 +139,6 @@ linters: - models testifylint: - disable: - - encoded-compare - - equal-values - - expected-actual - - float-compare - - go-require - - nil-compare - - require-error - - suite-dont-use-pkg - - suite-extra-assert-call # Intentionally enable all testifylint rules so new checks are adopted automatically. enable-all: true diff --git a/internal/chart/v3/lint/lint_test.go b/internal/chart/v3/lint/lint_test.go index afacb8052..6a2558fe0 100644 --- a/internal/chart/v3/lint/lint_test.go +++ b/internal/chart/v3/lint/lint_test.go @@ -22,6 +22,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/internal/chart/v3/lint/support" chartutil "helm.sh/helm/v4/internal/chart/v3/util" @@ -126,7 +127,7 @@ func TestBadCrdFileV3(t *testing.T) { var values map[string]any m := RunAll(badCrdFileDir, values, namespace).Messages assert.Lenf(t, m, 2, "All didn't fail with expected errors, got %#v", m) - assert.ErrorContains(t, m[0].Err, "apiVersion is not in 'apiextensions.k8s.io'") + require.ErrorContains(t, m[0].Err, "apiVersion is not in 'apiextensions.k8s.io'") assert.ErrorContains(t, m[1].Err, "object kind is not 'CustomResourceDefinition'") } diff --git a/internal/fileutil/fileutil_test.go b/internal/fileutil/fileutil_test.go index c78f70a3d..9c997fcd3 100644 --- a/internal/fileutil/fileutil_test.go +++ b/internal/fileutil/fileutil_test.go @@ -38,7 +38,7 @@ func TestAtomicWriteFile(t *testing.T) { mode := os.FileMode(0o644) err := AtomicWriteFile(testpath, reader, mode) - assert.NoError(t, err) + require.NoError(t, err) got, err := os.ReadFile(testpath) require.NoError(t, err) @@ -71,7 +71,7 @@ func TestAtomicWriteFile_EmptyContent(t *testing.T) { mode := os.FileMode(0o644) err := AtomicWriteFile(testpath, reader, mode) - assert.NoError(t, err, "AtomicWriteFile error with empty content") + require.NoError(t, err, "AtomicWriteFile error with empty content") got, err := os.ReadFile(testpath) require.NoError(t, err) @@ -90,7 +90,7 @@ func TestAtomicWriteFile_LargeContent(t *testing.T) { mode := os.FileMode(0o644) err := AtomicWriteFile(testpath, reader, mode) - assert.NoError(t, err, "AtomicWriteFile error with large content") + require.NoError(t, err, "AtomicWriteFile error with large content") got, err := os.ReadFile(testpath) require.NoError(t, err) diff --git a/internal/logging/logging_test.go b/internal/logging/logging_test.go index d22a47a31..83979e9db 100644 --- a/internal/logging/logging_test.go +++ b/internal/logging/logging_test.go @@ -24,6 +24,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestLogHolder_Logger(t *testing.T) { @@ -198,7 +199,7 @@ func TestDebugCheckHandler_Handle(t *testing.T) { record := slog.NewRecord(time.Now(), slog.LevelInfo, "test message", 0) err := handler.Handle(t.Context(), record) - assert.NoError(t, err) + require.NoError(t, err) assert.Contains(t, buf.String(), "test message") }) @@ -215,7 +216,7 @@ func TestDebugCheckHandler_Handle(t *testing.T) { record := slog.NewRecord(time.Now(), slog.LevelInfo, "context test", 0) err := handler.Handle(ctx, record) - assert.NoError(t, err) + require.NoError(t, err) assert.Contains(t, buf.String(), "context test") }) } diff --git a/internal/plugin/loader_test.go b/internal/plugin/loader_test.go index d6ca472f3..cb0de968d 100644 --- a/internal/plugin/loader_test.go +++ b/internal/plugin/loader_test.go @@ -118,7 +118,7 @@ func TestLoadDir(t *testing.T) { require.NoError(t, err, "error loading plugin from %s", tc.dirname) assert.Equal(t, tc.dirname, plug.Dir()) - assert.EqualValues(t, tc.expect, plug.Metadata()) + assert.Equal(t, tc.expect, plug.Metadata()) }) } } diff --git a/internal/plugin/metadata_v1_test.go b/internal/plugin/metadata_v1_test.go index 17a02dac0..21a78549e 100644 --- a/internal/plugin/metadata_v1_test.go +++ b/internal/plugin/metadata_v1_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMetadataV1ValidateVersion(t *testing.T) { @@ -78,7 +79,7 @@ func TestMetadataV1ValidateVersion(t *testing.T) { m := base() m.Version = tc.version err := m.Validate() - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tc.errMsg) }) } diff --git a/internal/plugin/runtime_extismv1_test.go b/internal/plugin/runtime_extismv1_test.go index 9451c693b..290d418ea 100644 --- a/internal/plugin/runtime_extismv1_test.go +++ b/internal/plugin/runtime_extismv1_test.go @@ -71,7 +71,7 @@ func TestRuntimeExtismV1InvokePlugin(t *testing.T) { p, err := r.CreatePlugin(pr.Dir, &pr.Metadata) - assert.NoError(t, err, "expected no error creating plugin") + require.NoError(t, err, "expected no error creating plugin") assert.NotNil(t, p, "expected plugin to be created") output, err := p.Invoke(t.Context(), &Input{ diff --git a/internal/statusreaders/job_status_reader_test.go b/internal/statusreaders/job_status_reader_test.go index 6e9ed5a79..fff0fcf3f 100644 --- a/internal/statusreaders/job_status_reader_test.go +++ b/internal/statusreaders/job_status_reader_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -107,9 +108,9 @@ func TestJobConditions(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() us, err := toUnstructured(t, tc.job) - assert.NoError(t, err) + require.NoError(t, err) result, err := jobConditions(us) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tc.expectedStatus, result.Status) }) } diff --git a/internal/statusreaders/pod_status_reader_test.go b/internal/statusreaders/pod_status_reader_test.go index ba0d1f1bb..76fb77afe 100644 --- a/internal/statusreaders/pod_status_reader_test.go +++ b/internal/statusreaders/pod_status_reader_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -102,9 +103,9 @@ func TestPodConditions(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() us, err := toUnstructured(t, tc.pod) - assert.NoError(t, err) + require.NoError(t, err) result, err := podConditions(us) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tc.expectedStatus, result.Status) }) } diff --git a/internal/tlsutil/tls_test.go b/internal/tlsutil/tls_test.go index 324b00969..b1b0f7d85 100644 --- a/internal/tlsutil/tls_test.go +++ b/internal/tlsutil/tls_test.go @@ -20,7 +20,6 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -51,7 +50,7 @@ func TestNewTLSConfig(t *testing.T) { WithCertKeyPairFiles(certFile, keyFile), WithCAFile(caCertFile), ) - assert.NoError(t, err) + require.NoError(t, err) require.Len(t, cfg.Certificates, 1) require.False(t, cfg.InsecureSkipVerify, "insecure skip verify mismatch, expecting false") @@ -62,7 +61,7 @@ func TestNewTLSConfig(t *testing.T) { WithInsecureSkipVerify(insecureSkipTLSVerify), WithCAFile(caCertFile), ) - assert.NoError(t, err) + require.NoError(t, err) require.Empty(t, cfg.Certificates) require.False(t, cfg.InsecureSkipVerify, "insecure skip verify mismatch, expecting false") @@ -74,7 +73,7 @@ func TestNewTLSConfig(t *testing.T) { WithInsecureSkipVerify(insecureSkipTLSVerify), WithCertKeyPairFiles(certFile, keyFile), ) - assert.NoError(t, err) + require.NoError(t, err) require.Len(t, cfg.Certificates, 1) require.False(t, cfg.InsecureSkipVerify, "insecure skip verify mismatch, expecting false") diff --git a/internal/urlutil/urlutil_test.go b/internal/urlutil/urlutil_test.go index d6b3f5256..27a646b58 100644 --- a/internal/urlutil/urlutil_test.go +++ b/internal/urlutil/urlutil_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestURLJoin(t *testing.T) { @@ -36,7 +37,7 @@ func TestURLJoin(t *testing.T) { for _, tt := range tests { got, err := URLJoin(tt.url, tt.paths...) - assert.NoError(t, err, tt.name) + require.NoError(t, err, tt.name) assert.Equal(t, tt.expect, got, tt.name) } } diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index ecf160500..045fa7d1e 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -358,10 +358,10 @@ func TestConfiguration_Init(t *testing.T) { actualErr := cfg.Init(nil, "default", tt.helmDriver) if tt.expectErr { - assert.Error(t, actualErr) + require.Error(t, actualErr) assert.Contains(t, actualErr.Error(), tt.errMsg) } else { - assert.NoError(t, actualErr) + require.NoError(t, actualErr) assert.IsType(t, tt.expectedDriverType, cfg.Releases.Driver) } }) @@ -1575,10 +1575,10 @@ data: merged, err := annotateAndMerge(tt.files) if tt.expectedError != "" { - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.expectedError) } else { - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, merged) assert.Equal(t, tt.expected, merged) } @@ -1738,10 +1738,10 @@ data: files, err := splitAndDeannotate(tt.input, "test") if tt.expectedError != "" { - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.expectedError) } else { - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, files, len(tt.expectedFiles)) for expectedFile, expectedContent := range tt.expectedFiles { @@ -1827,7 +1827,7 @@ func TestRenderResources_PostRenderer_Success(t *testing.T) { mockPR, false, false, false, PostRenderStrategyCombined, ) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, hooks) assert.NotNil(t, buf) assert.Empty(t, notes) @@ -1874,7 +1874,7 @@ func TestRenderResources_PostRenderer_Error(t *testing.T) { mockPR, false, false, false, PostRenderStrategyCombined, ) - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), "error while running post render on files") } @@ -1902,7 +1902,7 @@ func TestRenderResources_PostRenderer_MergeError(t *testing.T) { mockPR, false, false, false, PostRenderStrategyCombined, ) - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), "error merging manifests") } @@ -1924,7 +1924,7 @@ func TestRenderResources_PostRenderer_SplitError(t *testing.T) { mockPR, false, false, false, PostRenderStrategyCombined, ) - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), "error while parsing post rendered output: error parsing YAML: MalformedYAMLError:") } @@ -1945,7 +1945,7 @@ func TestRenderResources_PostRenderer_Integration(t *testing.T) { mockPR, false, false, false, PostRenderStrategyCombined, ) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, hooks) assert.NotNil(t, buf) assert.Empty(t, notes) // Notes should be empty for this test @@ -1984,7 +1984,7 @@ func TestRenderResources_NoPostRenderer(t *testing.T) { nil, false, false, false, PostRenderStrategyCombined, ) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, hooks) assert.NotNil(t, buf) assert.Empty(t, notes) @@ -2045,7 +2045,7 @@ spec: mockPR, false, false, false, PostRenderStrategySeparate, ) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, hooks, 1) assert.Equal(t, "my-app", hooks[0].Name) assert.Contains(t, buf.String(), "kind: Deployment") @@ -2087,7 +2087,7 @@ metadata: mockPR, false, false, false, PostRenderStrategyCombined, ) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 1, calls, "combined strategy should invoke the post-renderer exactly once") assert.Contains(t, lastInput, "hook-cm") assert.Contains(t, lastInput, "template-cm") @@ -2123,7 +2123,7 @@ metadata: mockPR, false, false, false, PostRenderStrategy(""), ) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 1, calls, "unset strategy must preserve backwards-compatible combined behavior") } @@ -2157,7 +2157,7 @@ metadata: mockPR, false, false, false, PostRenderStrategySeparate, ) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, inputs, 2, "separate strategy should invoke the post-renderer twice when both hooks and templates exist") for _, in := range inputs { hasHook := strings.Contains(in, "hook-cm") @@ -2191,7 +2191,7 @@ metadata: mockPR, false, false, false, PostRenderStrategySeparate, ) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 1, calls, "separate strategy should skip the empty hook group and invoke the post-renderer only once") } @@ -2225,7 +2225,7 @@ metadata: mockPR, false, false, false, PostRenderStrategyNoHooks, ) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, inputs, 1, "nohooks strategy should invoke the post-renderer exactly once (for templates only)") assert.NotContains(t, inputs[0], "hook-cm", "hooks must not be sent to the post-renderer") assert.Contains(t, inputs[0], "template-cm", "templates must be sent to the post-renderer") @@ -2262,7 +2262,7 @@ metadata: mockPR, false, false, false, PostRenderStrategyNoHooks, ) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 0, calls, "nohooks strategy should not invoke the post-renderer when the chart only has hooks") } @@ -2284,7 +2284,7 @@ metadata: mockPR, false, false, false, PostRenderStrategy("bogus"), ) - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), "unknown post-render strategy") assert.Contains(t, err.Error(), "bogus") } diff --git a/pkg/action/dependency_test.go b/pkg/action/dependency_test.go index 3899c20fc..25664b98c 100644 --- a/pkg/action/dependency_test.go +++ b/pkg/action/dependency_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/internal/test" chart "helm.sh/helm/v4/pkg/chart/v2" @@ -118,13 +119,14 @@ func TestStatArchiveForStatus(t *testing.T) { } is := assert.New(t) + req := require.New(t) lilithpath := filepath.Join(chartpath, "lilith-1.2.3.tgz") is.Empty(statArchiveForStatus(lilithpath, dep)) // save the chart (version 0.1.0, because that is the default) where, err := chartutil.Save(lilith, chartpath) - is.NoError(err) + req.NoError(err) // Should get "wrong version" because we asked for 1.2.3 and got 0.1.0 is.Equal("wrong version", statArchiveForStatus(where, dep)) diff --git a/pkg/action/get_metadata_test.go b/pkg/action/get_metadata_test.go index b488abf64..ee0bd84a0 100644 --- a/pkg/action/get_metadata_test.go +++ b/pkg/action/get_metadata_test.go @@ -443,7 +443,7 @@ func TestGetMetadata_Run_UnreachableKubeClient(t *testing.T) { client := NewGetMetadata(cfg) _, err := client.Run("test-release") - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), "connection refused") } @@ -452,7 +452,7 @@ func TestGetMetadata_Run_ReleaseNotFound(t *testing.T) { client := NewGetMetadata(cfg) _, err := client.Run("non-existent-release") - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), "not found") } @@ -648,10 +648,10 @@ func TestGetMetadata_Labels(t *testing.T) { metaGetter := NewGetMetadata(actionConfigFixture(t)) err := metaGetter.cfg.Releases.Create(rel) - assert.NoError(t, err) + require.NoError(t, err) metadata, err := metaGetter.Run(rel.Name) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, metadata.Name, rel.Name) assert.Equal(t, metadata.Labels, customLabels) diff --git a/pkg/action/get_values_test.go b/pkg/action/get_values_test.go index c4cbdf2d5..ce937b234 100644 --- a/pkg/action/get_values_test.go +++ b/pkg/action/get_values_test.go @@ -177,7 +177,7 @@ func TestGetValues_Run_UnreachableKubeClient(t *testing.T) { client := NewGetValues(cfg) _, err := client.Run("test-release") - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), "connection refused") } @@ -186,7 +186,7 @@ func TestGetValues_Run_ReleaseNotFound(t *testing.T) { client := NewGetValues(cfg) _, err := client.Run("non-existent-release") - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), "not found") } diff --git a/pkg/action/hooks_test.go b/pkg/action/hooks_test.go index b017b1f11..20442af13 100644 --- a/pkg/action/hooks_test.go +++ b/pkg/action/hooks_test.go @@ -28,6 +28,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/yaml" @@ -177,6 +178,7 @@ func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace str expectedOutput = "attempted to output logs for namespace: " + expectedNamespace } is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.ReleaseName = "failed-hooks" outBuffer := &bytes.Buffer{} @@ -190,9 +192,9 @@ func runInstallForHooksWithSuccess(t *testing.T, manifest, expectedNamespace str vals := map[string]any{} resi, err := instAction.Run(buildChartWithTemplates(templates), vals) - is.NoError(err) + req.NoError(err) res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.Equal(expectedOutput, outBuffer.String()) is.Equal(rcommon.StatusDeployed, res.Info.Status) } @@ -204,6 +206,7 @@ func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace str expectedOutput = "attempted to output logs for namespace: " + expectedNamespace } is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.ReleaseName = "failed-hooks" failingClient := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) @@ -220,9 +223,9 @@ func runInstallForHooksWithFailure(t *testing.T, manifest, expectedNamespace str vals := map[string]any{} resi, err := instAction.Run(buildChartWithTemplates(templates), vals) - is.Error(err) + req.Error(err) res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.Contains(res.Info.Description, "failed pre-install") is.Equal(expectedOutput, outBuffer.String()) is.Equal(rcommon.StatusFailed, res.Info.Status) @@ -447,6 +450,7 @@ func TestConfiguration_hookSetDeletePolicy(t *testing.T) { func TestExecHook_WaitOptionsPassedDownstream(t *testing.T) { is := assert.New(t) + req := require.New(t) failer := &kubefake.FailingKubeClient{ PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, @@ -487,7 +491,7 @@ data: waitOptions := []kube.WaitOption{kube.WithWaitContext(ctx)} err := configuration.execHook(rel, release.HookPreInstall, kube.StatusWatcherStrategy, waitOptions, 600, false) - is.NoError(err) + req.NoError(err) // Verify that WaitOptions were passed to GetWaiter is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 705a8e11d..28f989524 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -189,25 +189,25 @@ func TestInstallRelease(t *testing.T) { t.Fatalf("Failed install: %s", err) } res, err := releaserToV1Release(resi) - is.NoError(err) - is.Equal(res.Name, "test-install-release", "Expected release name.") - is.Equal(res.Namespace, "spaced") + req.NoError(err) + is.Equal("test-install-release", res.Name, "Expected release name.") + is.Equal("spaced", res.Namespace) r, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) + req.NoError(err) rel, err := releaserToV1Release(r) - is.NoError(err) + req.NoError(err) is.Len(rel.Hooks, 1) - is.Equal(rel.Hooks[0].Manifest, manifestWithHook) - is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall) - is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete") + is.Equal(manifestWithHook, rel.Hooks[0].Manifest) + is.Equal(release.HookPostInstall, rel.Hooks[0].Events[0]) + is.Equal(release.HookPreDelete, rel.Hooks[0].Events[1], "Expected event 0 is pre-delete") is.NotEmpty(res.Manifest) is.NotEmpty(rel.Manifest) is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") - is.Equal(rel.Info.Description, "Install complete") + is.Equal("Install complete", rel.Info.Description) // Detecting previous bug where context termination after successful release // caused release to fail. @@ -216,8 +216,8 @@ func TestInstallRelease(t *testing.T) { lastRelease, err := instAction.cfg.Releases.Last(rel.Name) req.NoError(err) lrel, err := releaserToV1Release(lastRelease) - is.NoError(err) - is.Equal(lrel.Info.Status, rcommon.StatusDeployed) + req.NoError(err) + is.Equal(rcommon.StatusDeployed, lrel.Info.Status) } func TestInstallReleaseWithTakeOwnership_ResourceNotOwned(t *testing.T) { @@ -231,6 +231,7 @@ func TestInstallReleaseWithTakeOwnership_ResourceNotOwned(t *testing.T) { // "Client{Namespace: namespace, kubeClient: k8sfake.NewClientset()}" is := assert.New(t) + req := require.New(t) // Resource list from cluster is NOT owned by helm chart config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false)) @@ -241,19 +242,20 @@ func TestInstallReleaseWithTakeOwnership_ResourceNotOwned(t *testing.T) { t.Fatalf("Failed install: %s", err) } res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) r, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) + req.NoError(err) rel, err := releaserToV1Release(r) - is.NoError(err) + req.NoError(err) - is.Equal(rel.Info.Description, "Install complete") + is.Equal("Install complete", rel.Info.Description) } func TestInstallReleaseWithTakeOwnership_ResourceOwned(t *testing.T) { is := assert.New(t) + req := require.New(t) // Resource list from cluster is owned by helm chart config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(true)) @@ -264,29 +266,31 @@ func TestInstallReleaseWithTakeOwnership_ResourceOwned(t *testing.T) { t.Fatalf("Failed install: %s", err) } res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) r, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) + req.NoError(err) rel, err := releaserToV1Release(r) - is.NoError(err) + req.NoError(err) - is.Equal(rel.Info.Description, "Install complete") + is.Equal("Install complete", rel.Info.Description) } func TestInstallReleaseWithTakeOwnership_ResourceOwnedNoFlag(t *testing.T) { is := assert.New(t) + req := require.New(t) // Resource list from cluster is NOT owned by helm chart config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false)) instAction := installActionWithConfig(config) _, err := instAction.Run(buildChart(), nil) - is.Error(err) + req.Error(err) is.Contains(err.Error(), "unable to continue with install") } func TestInstallReleaseWithValues(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) userVals := map[string]any{ "nestedKey": map[string]any{ @@ -303,20 +307,20 @@ func TestInstallReleaseWithValues(t *testing.T) { t.Fatalf("Failed install: %s", err) } res, err := releaserToV1Release(resi) - is.NoError(err) - is.Equal(res.Name, "test-install-release", "Expected release name.") - is.Equal(res.Namespace, "spaced") + req.NoError(err) + is.Equal("test-install-release", res.Name, "Expected release name.") + is.Equal("spaced", res.Namespace) r, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) + req.NoError(err) rel, err := releaserToV1Release(r) - is.NoError(err) + req.NoError(err) is.Len(rel.Hooks, 1) - is.Equal(rel.Hooks[0].Manifest, manifestWithHook) - is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall) - is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete") + is.Equal(manifestWithHook, rel.Hooks[0].Manifest) + is.Equal(release.HookPostInstall, rel.Hooks[0].Events[0]) + is.Equal(release.HookPreDelete, rel.Hooks[0].Events[1], "Expected event 0 is pre-delete") is.NotEmpty(res.Manifest) is.NotEmpty(rel.Manifest) @@ -338,6 +342,7 @@ func TestInstallRelease_NoName(t *testing.T) { func TestInstallRelease_WithNotes(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.ReleaseName = "with-notes" vals := map[string]any{} @@ -346,29 +351,30 @@ func TestInstallRelease_WithNotes(t *testing.T) { t.Fatalf("Failed install: %s", err) } res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) - is.Equal(res.Name, "with-notes") - is.Equal(res.Namespace, "spaced") + is.Equal("with-notes", res.Name) + is.Equal("spaced", res.Namespace) r, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) + req.NoError(err) rel, err := releaserToV1Release(r) - is.NoError(err) + req.NoError(err) is.Len(rel.Hooks, 1) - is.Equal(rel.Hooks[0].Manifest, manifestWithHook) - is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall) - is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete") + is.Equal(manifestWithHook, rel.Hooks[0].Manifest) + is.Equal(release.HookPostInstall, rel.Hooks[0].Events[0]) + is.Equal(release.HookPreDelete, rel.Hooks[0].Events[1], "Expected event 0 is pre-delete") is.NotEmpty(res.Manifest) is.NotEmpty(rel.Manifest) is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") - is.Equal(rel.Info.Description, "Install complete") + is.Equal("Install complete", rel.Info.Description) - is.Equal(rel.Info.Notes, "note here") + is.Equal("note here", rel.Info.Notes) } func TestInstallRelease_WithNotesRendered(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.ReleaseName = "with-notes" vals := map[string]any{} @@ -377,21 +383,22 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) { t.Fatalf("Failed install: %s", err) } res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) r, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) + req.NoError(err) rel, err := releaserToV1Release(r) - is.NoError(err) + req.NoError(err) expectedNotes := "got-" + res.Name is.Equal(expectedNotes, rel.Info.Notes) - is.Equal(rel.Info.Description, "Install complete") + is.Equal("Install complete", rel.Info.Description) } func TestInstallRelease_WithChartAndDependencyParentNotes(t *testing.T) { // Regression: Make sure that the child's notes don't override the parent's is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.ReleaseName = "with-notes" vals := map[string]any{} @@ -400,20 +407,21 @@ func TestInstallRelease_WithChartAndDependencyParentNotes(t *testing.T) { t.Fatalf("Failed install: %s", err) } res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) r, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) + req.NoError(err) rel, err := releaserToV1Release(r) - is.NoError(err) + req.NoError(err) is.Equal("with-notes", rel.Name) is.Equal("parent", rel.Info.Notes) - is.Equal(rel.Info.Description, "Install complete") + is.Equal("Install complete", rel.Info.Description) } func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) { // Regression: Make sure that the child's notes don't override the parent's is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.ReleaseName = "with-notes" instAction.SubNotes = true @@ -423,23 +431,24 @@ func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) { t.Fatalf("Failed install: %s", err) } res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) r, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) + req.NoError(err) rel, err := releaserToV1Release(r) - is.NoError(err) + req.NoError(err) is.Equal("with-notes", rel.Name) // test run can return as either 'parent\nchild' or 'child\nparent' if !strings.Contains(rel.Info.Notes, "parent") && !strings.Contains(rel.Info.Notes, "child") { t.Fatalf("Expected 'parent\nchild' or 'child\nparent', got '%s'", rel.Info.Notes) } - is.Equal(rel.Info.Description, "Install complete") + is.Equal("Install complete", rel.Info.Description) } func TestInstallRelease_DryRunClient(t *testing.T) { for _, dryRunStrategy := range []DryRunStrategy{DryRunClient, DryRunServer} { is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.DryRunStrategy = dryRunStrategy @@ -449,7 +458,7 @@ func TestInstallRelease_DryRunClient(t *testing.T) { t.Fatalf("Failed install: %s", err) } res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.Contains(res.Manifest, "---\n# Source: hello/templates/hello\nhello: world") is.Contains(res.Manifest, "---\n# Source: hello/templates/goodbye\ngoodbye: world") @@ -458,15 +467,16 @@ func TestInstallRelease_DryRunClient(t *testing.T) { is.NotContains(res.Manifest, "empty") _, err = instAction.cfg.Releases.Get(res.Name, res.Version) - is.Error(err) + req.Error(err) is.Len(res.Hooks, 1) is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "expect hook to not be marked as run") - is.Equal(res.Info.Description, "Dry run complete") + is.Equal("Dry run complete", res.Info.Description) } } func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) // First perform a normal dry-run with the secret and confirm its presence. @@ -477,12 +487,12 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { t.Fatalf("Failed install: %s", err) } res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.Contains(res.Manifest, "---\n# Source: hello/templates/secret.yaml\napiVersion: v1\nkind: Secret") _, err = instAction.cfg.Releases.Get(res.Name, res.Version) - is.Error(err) - is.Equal(res.Info.Description, "Dry run complete") + req.Error(err) + is.Equal("Dry run complete", res.Info.Description) // Perform a dry-run where the secret should not be present instAction.HideSecret = true @@ -492,13 +502,13 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { t.Fatalf("Failed install: %s", err) } res2, err := releaserToV1Release(res2i) - is.NoError(err) + req.NoError(err) is.NotContains(res2.Manifest, "---\n# Source: hello/templates/secret.yaml\napiVersion: v1\nkind: Secret") _, err = instAction.cfg.Releases.Get(res2.Name, res2.Version) - is.Error(err) - is.Equal(res2.Info.Description, "Dry run complete") + req.Error(err) + is.Equal("Dry run complete", res2.Info.Description) // Ensure there is an error when HideSecret True but not in a dry-run mode instAction.DryRunStrategy = DryRunNone @@ -512,6 +522,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { // Regression test for #7955 func TestInstallRelease_DryRun_Lookup(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.DryRunStrategy = DryRunNone vals := map[string]any{} @@ -528,7 +539,7 @@ func TestInstallRelease_DryRun_Lookup(t *testing.T) { t.Fatalf("Failed install: %s", err) } res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.Contains(res.Manifest, "goodbye: map[]") } @@ -550,6 +561,7 @@ func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { func TestInstallRelease_NoHooks(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.DisableHooks = true instAction.ReleaseName = "no-hooks" @@ -561,13 +573,14 @@ func TestInstallRelease_NoHooks(t *testing.T) { t.Fatalf("Failed install: %s", err) } res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "hooks should not run with no-hooks") } func TestInstallRelease_FailedHooks(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.ReleaseName = "failed-hooks" failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) @@ -578,9 +591,9 @@ func TestInstallRelease_FailedHooks(t *testing.T) { vals := map[string]any{} resi, err := instAction.Run(buildChart(), vals) - is.Error(err) + req.Error(err) res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.Contains(res.Info.Description, "failed post-install") is.Empty(outBuffer.String()) is.Equal(rcommon.StatusFailed, res.Info.Status) @@ -588,48 +601,51 @@ func TestInstallRelease_FailedHooks(t *testing.T) { func TestInstallRelease_ReplaceRelease(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.Replace = true rel := releaseStub() rel.Info.Status = rcommon.StatusUninstalled - require.NoError(t, instAction.cfg.Releases.Create(rel)) + req.NoError(instAction.cfg.Releases.Create(rel)) instAction.ReleaseName = rel.Name vals := map[string]any{} resi, err := instAction.Run(buildChart(), vals) - is.NoError(err) + req.NoError(err) res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) // This should have been auto-incremented is.Equal(2, res.Version) is.Equal(res.Name, rel.Name) r, err := instAction.cfg.Releases.Get(rel.Name, res.Version) - is.NoError(err) + req.NoError(err) getres, err := releaserToV1Release(r) - is.NoError(err) - is.Equal(getres.Info.Status, rcommon.StatusDeployed) + req.NoError(err) + is.Equal(rcommon.StatusDeployed, getres.Info.Status) } func TestInstallRelease_KubeVersion(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) vals := map[string]any{} _, err := instAction.Run(buildChart(withKube(">=0.0.0")), vals) - is.NoError(err) + req.NoError(err) // This should fail for a few hundred years instAction.ReleaseName = "should-fail" vals = map[string]any{} _, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals) - is.Error(err) + req.Error(err) is.Contains(err.Error(), "chart requires kubeVersion: >=99.0.0 which is incompatible with Kubernetes v1.20.") } func TestInstallRelease_Wait(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.ReleaseName = "come-fail-away" failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) @@ -641,16 +657,17 @@ func TestInstallRelease_Wait(t *testing.T) { goroutines := instAction.getGoroutineCount() resi, err := instAction.Run(buildChart(), vals) - is.Error(err) + req.Error(err) res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.Contains(res.Info.Description, "I timed out") - is.Equal(res.Info.Status, rcommon.StatusFailed) + is.Equal(rcommon.StatusFailed, res.Info.Status) is.Equal(goroutines, instAction.getGoroutineCount()) } func TestInstallRelease_Wait_Interrupted(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.ReleaseName = "interrupted-release" failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) @@ -665,7 +682,7 @@ func TestInstallRelease_Wait_Interrupted(t *testing.T) { goroutines := instAction.getGoroutineCount() _, err := instAction.RunWithContext(ctx, buildChart(), vals) - is.Error(err) + req.Error(err) is.Contains(err.Error(), "context canceled") is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background @@ -674,6 +691,7 @@ func TestInstallRelease_Wait_Interrupted(t *testing.T) { } func TestInstallRelease_WaitForJobs(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.ReleaseName = "come-fail-away" failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) @@ -684,17 +702,17 @@ func TestInstallRelease_WaitForJobs(t *testing.T) { vals := map[string]any{} resi, err := instAction.Run(buildChart(), vals) - is.Error(err) + req.Error(err) res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.Contains(res.Info.Description, "I timed out") - is.Equal(res.Info.Status, rcommon.StatusFailed) + is.Equal(rcommon.StatusFailed, res.Info.Status) } func TestInstallRelease_RollbackOnFailure(t *testing.T) { - is := assert.New(t) - t.Run("rollback-on-failure uninstall succeeds", func(t *testing.T) { + is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.ReleaseName = "come-fail-away" failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) @@ -707,19 +725,21 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) { vals := map[string]any{} resi, err := instAction.Run(buildChart(), vals) - is.Error(err) + req.Error(err) is.Contains(err.Error(), "I timed out") is.Contains(err.Error(), "rollback-on-failure") res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) // Now make sure it isn't in storage anymore _, err = instAction.cfg.Releases.Get(res.Name, res.Version) - is.Error(err) + req.Error(err) is.Equal(err, driver.ErrReleaseNotFound) }) t.Run("rollback-on-failure uninstall fails", func(t *testing.T) { + is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.ReleaseName = "come-fail-away-with-me" failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) @@ -730,7 +750,7 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) { vals := map[string]any{} _, err := instAction.Run(buildChart(), vals) - is.Error(err) + req.Error(err) is.Contains(err.Error(), "I timed out") is.Contains(err.Error(), "uninstall fail") is.Contains(err.Error(), "an error occurred while uninstalling the release") @@ -738,6 +758,7 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) { } func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.ReleaseName = "interrupted-release" failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) @@ -752,16 +773,16 @@ func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { goroutines := instAction.getGoroutineCount() resi, err := instAction.RunWithContext(ctx, buildChart(), vals) - is.Error(err) + req.Error(err) is.Contains(err.Error(), "context canceled") is.Contains(err.Error(), "rollback-on-failure") is.Contains(err.Error(), "uninstalled") res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) // Now make sure it isn't in storage anymore _, err = instAction.cfg.Releases.Get(res.Name, res.Version) - is.Error(err) + req.Error(err) is.Equal(err, driver.ErrReleaseNotFound) is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background time.Sleep(10 * time.Second) // wait for goroutine to finish @@ -837,6 +858,7 @@ func TestNameTemplate(t *testing.T) { func TestInstallReleaseOutputDir(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) vals := map[string]any{} @@ -850,16 +872,16 @@ func TestInstallReleaseOutputDir(t *testing.T) { } _, err = os.Stat(filepath.Join(dir, "hello/templates/goodbye")) - is.NoError(err) + req.NoError(err) _, err = os.Stat(filepath.Join(dir, "hello/templates/hello")) - is.NoError(err) + req.NoError(err) _, err = os.Stat(filepath.Join(dir, "hello/templates/with-partials")) - is.NoError(err) + req.NoError(err) _, err = os.Stat(filepath.Join(dir, "hello/templates/rbac")) - is.NoError(err) + req.NoError(err) test.AssertGoldenFile(t, filepath.Join(dir, "hello/templates/rbac"), "rbac.txt") @@ -869,6 +891,7 @@ func TestInstallReleaseOutputDir(t *testing.T) { func TestInstallOutputDirWithReleaseName(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) vals := map[string]any{} @@ -886,16 +909,16 @@ func TestInstallOutputDirWithReleaseName(t *testing.T) { } _, err = os.Stat(filepath.Join(newDir, "hello/templates/goodbye")) - is.NoError(err) + req.NoError(err) _, err = os.Stat(filepath.Join(newDir, "hello/templates/hello")) - is.NoError(err) + req.NoError(err) _, err = os.Stat(filepath.Join(newDir, "hello/templates/with-partials")) - is.NoError(err) + req.NoError(err) _, err = os.Stat(filepath.Join(newDir, "hello/templates/rbac")) - is.NoError(err) + req.NoError(err) test.AssertGoldenFile(t, filepath.Join(newDir, "hello/templates/rbac"), "rbac.txt") @@ -1008,6 +1031,7 @@ func TestNameAndChartGenerateName(t *testing.T) { func TestInstallWithLabels(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.Labels = map[string]string{ "key1": "val1", @@ -1018,7 +1042,7 @@ func TestInstallWithLabels(t *testing.T) { t.Fatalf("Failed install: %s", err) } res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.Equal(instAction.Labels, res.Labels) } @@ -1275,6 +1299,7 @@ func TestInstallCRDs_CheckNilErrors(t *testing.T) { func TestInstallRelease_WaitOptionsPassedDownstream(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.ReleaseName = "wait-options-test" @@ -1289,7 +1314,7 @@ func TestInstallRelease_WaitOptionsPassedDownstream(t *testing.T) { vals := map[string]any{} _, err := instAction.Run(buildChart(), vals) - is.NoError(err) + req.NoError(err) // Verify that WaitOptions were passed to GetWaiter is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") diff --git a/pkg/action/list_test.go b/pkg/action/list_test.go index 19596dbbb..e7c2770d7 100644 --- a/pkg/action/list_test.go +++ b/pkg/action/list_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" kubefake "helm.sh/helm/v4/pkg/kube/fake" ri "helm.sh/helm/v4/pkg/release" @@ -64,7 +65,7 @@ func TestListStates(t *testing.T) { func TestList_Empty(t *testing.T) { lister := NewList(actionConfigFixture(t)) list, err := lister.Run() - assert.NoError(t, err) + require.NoError(t, err) assert.Empty(t, list) } @@ -75,33 +76,36 @@ func newListFixture(t *testing.T) *List { func TestList_OneNamespace(t *testing.T) { is := assert.New(t) + req := require.New(t) lister := newListFixture(t) makeMeSomeReleases(t, lister.cfg.Releases) list, err := lister.Run() - is.NoError(err) + req.NoError(err) is.Len(list, 3) } func TestList_AllNamespaces(t *testing.T) { is := assert.New(t) + req := require.New(t) lister := newListFixture(t) makeMeSomeReleases(t, lister.cfg.Releases) lister.AllNamespaces = true lister.SetStateMask() list, err := lister.Run() - is.NoError(err) + req.NoError(err) is.Len(list, 3) } func TestList_Sort(t *testing.T) { is := assert.New(t) + req := require.New(t) lister := newListFixture(t) lister.Sort = ByNameDesc // Other sorts are tested elsewhere makeMeSomeReleases(t, lister.cfg.Releases) l, err := lister.Run() - is.NoError(err) + req.NoError(err) list, err := releaseListToV1List(l) - is.NoError(err) + req.NoError(err) is.Len(list, 3) is.Equal("two", list[0].Name) @@ -111,13 +115,14 @@ func TestList_Sort(t *testing.T) { func TestList_Limit(t *testing.T) { is := assert.New(t) + req := require.New(t) lister := newListFixture(t) lister.Limit = 2 makeMeSomeReleases(t, lister.cfg.Releases) l, err := lister.Run() - is.NoError(err) + req.NoError(err) list, err := releaseListToV1List(l) - is.NoError(err) + req.NoError(err) is.Len(list, 2) // Lex order means one, three, two is.Equal("one", list[0].Name) @@ -126,13 +131,14 @@ func TestList_Limit(t *testing.T) { func TestList_BigLimit(t *testing.T) { is := assert.New(t) + req := require.New(t) lister := newListFixture(t) lister.Limit = 20 makeMeSomeReleases(t, lister.cfg.Releases) l, err := lister.Run() - is.NoError(err) + req.NoError(err) list, err := releaseListToV1List(l) - is.NoError(err) + req.NoError(err) is.Len(list, 3) // Lex order means one, three, two @@ -143,14 +149,15 @@ func TestList_BigLimit(t *testing.T) { func TestList_LimitOffset(t *testing.T) { is := assert.New(t) + req := require.New(t) lister := newListFixture(t) lister.Limit = 2 lister.Offset = 1 makeMeSomeReleases(t, lister.cfg.Releases) l, err := lister.Run() - is.NoError(err) + req.NoError(err) list, err := releaseListToV1List(l) - is.NoError(err) + req.NoError(err) is.Len(list, 2) // Lex order means one, three, two @@ -160,27 +167,29 @@ func TestList_LimitOffset(t *testing.T) { func TestList_LimitOffsetOutOfBounds(t *testing.T) { is := assert.New(t) + req := require.New(t) lister := newListFixture(t) lister.Limit = 2 lister.Offset = 3 // Last item is index 2 makeMeSomeReleases(t, lister.cfg.Releases) list, err := lister.Run() - is.NoError(err) + req.NoError(err) is.Empty(list) lister.Limit = 10 lister.Offset = 1 list, err = lister.Run() - is.NoError(err) + req.NoError(err) is.Len(list, 2) } func TestList_StateMask(t *testing.T) { is := assert.New(t) + req := require.New(t) lister := newListFixture(t) makeMeSomeReleases(t, lister.cfg.Releases) oner, err := lister.cfg.Releases.Get("one", 1) - is.NoError(err) + req.NoError(err) var one release.Release switch v := oner.(type) { @@ -194,18 +203,18 @@ func TestList_StateMask(t *testing.T) { one.SetStatus(common.StatusUninstalled, "uninstalled") err = lister.cfg.Releases.Update(one) - is.NoError(err) + req.NoError(err) res, err := lister.Run() - is.NoError(err) + req.NoError(err) is.Len(res, 3) ac0, err := ri.NewAccessor(res[0]) - is.NoError(err) + req.NoError(err) ac1, err := ri.NewAccessor(res[1]) - is.NoError(err) + req.NoError(err) ac2, err := ri.NewAccessor(res[2]) - is.NoError(err) + req.NoError(err) is.Equal("one", ac0.Name()) is.Equal("three", ac1.Name()) @@ -213,20 +222,21 @@ func TestList_StateMask(t *testing.T) { lister.StateMask = ListUninstalled res, err = lister.Run() - is.NoError(err) + req.NoError(err) is.Len(res, 1) ac0, err = ri.NewAccessor(res[0]) - is.NoError(err) + req.NoError(err) is.Equal("one", ac0.Name()) lister.StateMask |= ListDeployed res, err = lister.Run() - is.NoError(err) + req.NoError(err) is.Len(res, 3) } func TestList_StateMaskWithStaleRevisions(t *testing.T) { is := assert.New(t) + req := require.New(t) lister := newListFixture(t) lister.StateMask = ListFailed @@ -234,13 +244,13 @@ func TestList_StateMaskWithStaleRevisions(t *testing.T) { res, err := lister.Run() - is.NoError(err) + req.NoError(err) is.Len(res, 1) // "dirty" release should _not_ be present as most recent // release is deployed despite failed release in past ac0, err := ri.NewAccessor(res[0]) - is.NoError(err) + req.NoError(err) is.Equal("failed", ac0.Name()) } @@ -273,21 +283,22 @@ func makeMeSomeReleasesWithStaleFailure(t *testing.T, store *storage.Storage) { } all, err := store.ListReleases() - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, all, 5, "sanity test: five items added") } func TestList_Filter(t *testing.T) { is := assert.New(t) + req := require.New(t) lister := newListFixture(t) lister.Filter = "th." makeMeSomeReleases(t, lister.cfg.Releases) res, err := lister.Run() - is.NoError(err) + req.NoError(err) is.Len(res, 1) ac0, err := ri.NewAccessor(res[0]) - is.NoError(err) + req.NoError(err) is.Equal("three", ac0.Name()) } @@ -323,7 +334,7 @@ func makeMeSomeReleases(t *testing.T, store *storage.Storage) { } all, err := store.ListReleases() - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, all, 3, "sanity test: three items added") } diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go index ccba4f847..9984b508a 100644 --- a/pkg/action/package_test.go +++ b/pkg/action/package_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/Masterminds/semver/v3" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "helm.sh/helm/v4/internal/test/ensure" @@ -92,8 +93,8 @@ func TestPassphraseFileFetcher_WithStdinAndMultipleFetches(t *testing.T) { passphrase := "secret-from-stdin" go func() { - _, err = w.WriteString(passphrase + "\n") - require.NoError(t, err) + _, err := w.WriteString(passphrase + "\n") + assert.NoError(t, err) }() for range 4 { diff --git a/pkg/action/pull_test.go b/pkg/action/pull_test.go index ba212973e..7cd5d3e85 100644 --- a/pkg/action/pull_test.go +++ b/pkg/action/pull_test.go @@ -71,8 +71,8 @@ func startLocalServerForTests(t *testing.T, handler http.Handler) (*httptest.Ser return nil, err } handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _, err = w.Write(fileBytes) - require.NoError(t, err) + _, err := w.Write(fileBytes) + assert.NoError(t, err) }) } diff --git a/pkg/action/registry_login_test.go b/pkg/action/registry_login_test.go index 474118c9f..408f16d84 100644 --- a/pkg/action/registry_login_test.go +++ b/pkg/action/registry_login_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewRegistryLogin(t *testing.T) { @@ -37,7 +38,7 @@ func TestWithCertFile(t *testing.T) { certFile := "testdata/cert.pem" opt := WithCertFile(certFile) - assert.NoError(t, opt(client)) + require.NoError(t, opt(client)) assert.Equal(t, certFile, client.certFile) } @@ -47,7 +48,7 @@ func TestWithInsecure(t *testing.T) { opt := WithInsecure(true) - assert.NoError(t, opt(client)) + require.NoError(t, opt(client)) assert.True(t, client.insecure) } @@ -58,7 +59,7 @@ func TestWithKeyFile(t *testing.T) { keyFile := "testdata/key.pem" opt := WithKeyFile(keyFile) - assert.NoError(t, opt(client)) + require.NoError(t, opt(client)) assert.Equal(t, keyFile, client.keyFile) } @@ -69,7 +70,7 @@ func TestWithCAFile(t *testing.T) { caFile := "testdata/ca.pem" opt := WithCAFile(caFile) - assert.NoError(t, opt(client)) + require.NoError(t, opt(client)) assert.Equal(t, caFile, client.caFile) } @@ -79,6 +80,6 @@ func TestWithPlainHTTPLogin(t *testing.T) { opt := WithPlainHTTPLogin(true) - assert.NoError(t, opt(client)) + require.NoError(t, opt(client)) assert.True(t, client.plainHTTP) } diff --git a/pkg/action/release_testing_test.go b/pkg/action/release_testing_test.go index 2ec62caf0..d2a365cba 100644 --- a/pkg/action/release_testing_test.go +++ b/pkg/action/release_testing_test.go @@ -147,6 +147,7 @@ func TestReleaseTestingGetPodLogs_SkipNonPodHooks(t *testing.T) { func TestReleaseTesting_WaitOptionsPassedDownstream(t *testing.T) { is := assert.New(t) + req := require.New(t) config := actionConfigFixture(t) // Create a release with a test hook @@ -165,7 +166,7 @@ func TestReleaseTesting_WaitOptionsPassedDownstream(t *testing.T) { failer := config.KubeClient.(*kubefake.FailingKubeClient) _, _, err := client.Run(rel.Name) - is.NoError(err) + req.NoError(err) // Verify that WaitOptions were passed to GetWaiter is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") diff --git a/pkg/action/rollback_test.go b/pkg/action/rollback_test.go index b34adda91..01419341b 100644 --- a/pkg/action/rollback_test.go +++ b/pkg/action/rollback_test.go @@ -49,6 +49,7 @@ func TestRollbackRun_UnreachableKubeClient(t *testing.T) { func TestRollback_WaitOptionsPassedDownstream(t *testing.T) { is := assert.New(t) + req := require.New(t) config := actionConfigFixture(t) // Create a deployed release and a second version to roll back to @@ -78,7 +79,7 @@ func TestRollback_WaitOptionsPassedDownstream(t *testing.T) { failer := config.KubeClient.(*kubefake.FailingKubeClient) err := client.Run(rel.Name) - is.NoError(err) + req.NoError(err) // Verify that WaitOptions were passed to GetWaiter is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") diff --git a/pkg/action/uninstall_test.go b/pkg/action/uninstall_test.go index 90f69d639..913ade1a5 100644 --- a/pkg/action/uninstall_test.go +++ b/pkg/action/uninstall_test.go @@ -61,6 +61,7 @@ func TestUninstallRelease_ignoreNotFound(t *testing.T) { } func TestUninstallRelease_deleteRelease(t *testing.T) { is := assert.New(t) + req := require.New(t) unAction := uninstallAction(t) unAction.DisableHooks = true @@ -83,9 +84,9 @@ func TestUninstallRelease_deleteRelease(t *testing.T) { "password": "password" } }` - require.NoError(t, unAction.cfg.Releases.Create(rel)) + req.NoError(unAction.cfg.Releases.Create(rel)) res, err := unAction.Run(rel.Name) - is.NoError(err) + req.NoError(err) expected := `These resources were kept due to the resource policy: [Secret] secret ` @@ -94,6 +95,7 @@ func TestUninstallRelease_deleteRelease(t *testing.T) { func TestUninstallRelease_Wait(t *testing.T) { is := assert.New(t) + req := require.New(t) unAction := uninstallAction(t) unAction.DisableHooks = true @@ -113,16 +115,16 @@ func TestUninstallRelease_Wait(t *testing.T) { "password": "password" } }` - require.NoError(t, unAction.cfg.Releases.Create(rel)) + req.NoError(unAction.cfg.Releases.Create(rel)) failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitForDeleteError = errors.New("U timed out") unAction.cfg.KubeClient = failer resi, err := unAction.Run(rel.Name) - is.Error(err) + req.Error(err) is.Contains(err.Error(), "U timed out") res, err := releaserToV1Release(resi.Release) - is.NoError(err) - is.Equal(res.Info.Status, common.StatusUninstalled) + req.NoError(err) + is.Equal(common.StatusUninstalled, res.Info.Status) } func TestUninstallRelease_Cascade(t *testing.T) { @@ -180,6 +182,7 @@ func TestUninstallRun_UnreachableKubeClient(t *testing.T) { func TestUninstallRelease_OwnershipVerification(t *testing.T) { is := assert.New(t) + req := require.New(t) // Create a buffer to capture log output logBuffer := &bytes.Buffer{} @@ -207,7 +210,7 @@ metadata: meta.helm.sh/release-namespace: default data: key: value` - require.NoError(t, config.Releases.Create(rel)) + req.NoError(config.Releases.Create(rel)) // Create dummy resources with proper ownership metadata labels := map[string]string{ @@ -224,10 +227,10 @@ data: failer.DummyResources = dummyResources resi, err := unAction.Run(rel.Name) - is.NoError(err) + req.NoError(err) is.NotNil(resi) res, err := releaserToV1Release(resi.Release) - is.NoError(err) + req.NoError(err) is.Equal(common.StatusUninstalled, res.Info.Status) // Verify log contains debug message about deleting owned resource @@ -239,6 +242,7 @@ data: func TestUninstallRelease_OwnershipVerification_WithKeepPolicy(t *testing.T) { is := assert.New(t) + req := require.New(t) // Create a buffer to capture log output logBuffer := &bytes.Buffer{} @@ -280,7 +284,7 @@ metadata: meta.helm.sh/release-namespace: default data: key: value` - require.NoError(t, config.Releases.Create(rel)) + req.NoError(config.Releases.Create(rel)) // Create dummy resources - one unowned to test logging dummyResources := kube.ResourceList{ @@ -290,7 +294,7 @@ data: failer.DummyResources = dummyResources res, err := unAction.Run(rel.Name) - is.NoError(err) + req.NoError(err) is.NotNil(res) // Should contain info about kept resources is.Contains(res.Info, "kept due to the resource policy") @@ -303,6 +307,7 @@ data: func TestUninstallRelease_DryRun_OwnershipVerification(t *testing.T) { is := assert.New(t) + req := require.New(t) // Create a buffer to capture log output logBuffer := &bytes.Buffer{} @@ -329,7 +334,7 @@ metadata: meta.helm.sh/release-namespace: default data: key: value` - require.NoError(t, config.Releases.Create(rel)) + req.NoError(config.Releases.Create(rel)) // Create dummy resources - one unowned to test dry-run logging dummyResources := kube.ResourceList{ @@ -339,11 +344,11 @@ data: failer.DummyResources = dummyResources resi, err := unAction.Run(rel.Name) - is.NoError(err) + req.NoError(err) is.NotNil(resi) is.NotNil(resi.Release) res, err := releaserToV1Release(resi.Release) - is.NoError(err) + req.NoError(err) is.Equal("dryrun-ownership", res.Name) // Verify log contains dry-run warning about resources that would be skipped diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index bd020cbdb..7a73c7179 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -66,8 +66,8 @@ func TestUpgradeRelease_Success(t *testing.T) { resi, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) req.NoError(err) res, err := releaserToV1Release(resi) - is.NoError(err) - is.Equal(res.Info.Status, common.StatusDeployed) + req.NoError(err) + is.Equal(common.StatusDeployed, res.Info.Status) done() // Detecting previous bug where context termination after successful release @@ -77,7 +77,7 @@ func TestUpgradeRelease_Success(t *testing.T) { req.NoError(err) lastRelease, err := releaserToV1Release(lastReleasei) req.NoError(err) - is.Equal(lastRelease.Info.Status, common.StatusDeployed) + is.Equal(common.StatusDeployed, lastRelease.Info.Status) } func TestUpgradeRelease_Wait(t *testing.T) { @@ -99,9 +99,9 @@ func TestUpgradeRelease_Wait(t *testing.T) { resi, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.Contains(res.Info.Description, "I timed out") - is.Equal(res.Info.Status, common.StatusFailed) + is.Equal(common.StatusFailed, res.Info.Status) } func TestUpgradeRelease_WaitForJobs(t *testing.T) { @@ -124,9 +124,9 @@ func TestUpgradeRelease_WaitForJobs(t *testing.T) { resi, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.Contains(res.Info.Description, "I timed out") - is.Equal(res.Info.Status, common.StatusFailed) + is.Equal(common.StatusFailed, res.Info.Status) } func TestUpgradeRelease_CleanupOnFail(t *testing.T) { @@ -151,22 +151,21 @@ func TestUpgradeRelease_CleanupOnFail(t *testing.T) { req.Error(err) is.NotContains(err.Error(), "unable to cleanup resources") res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.Contains(res.Info.Description, "I timed out") - is.Equal(res.Info.Status, common.StatusFailed) + is.Equal(common.StatusFailed, res.Info.Status) } func TestUpgradeRelease_RollbackOnFailure(t *testing.T) { - is := assert.New(t) - req := require.New(t) - t.Run("rollback-on-failure rollback succeeds", func(t *testing.T) { + is := assert.New(t) + req := require.New(t) upAction := upgradeAction(t) rel := releaseStub() rel.Name = "nuketown" rel.Info.Status = common.StatusDeployed - require.NoError(t, upAction.cfg.Releases.Create(rel)) + req.NoError(upAction.cfg.Releases.Create(rel)) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) // We can't make Update error because then the rollback won't work @@ -180,18 +179,20 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) { is.Contains(err.Error(), "arming key removed") is.Contains(err.Error(), "rollback-on-failure") res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) // Now make sure it is actually upgraded updatedResi, err := upAction.cfg.Releases.Get(res.Name, 3) - is.NoError(err) + req.NoError(err) updatedRes, err := releaserToV1Release(updatedResi) - is.NoError(err) + req.NoError(err) // Should have rolled back to the previous - is.Equal(updatedRes.Info.Status, common.StatusDeployed) + is.Equal(common.StatusDeployed, updatedRes.Info.Status) }) t.Run("rollback-on-failure uninstall fails", func(t *testing.T) { + is := assert.New(t) + req := require.New(t) upAction := upgradeAction(t) rel := releaseStub() rel.Name = "fallout" @@ -212,9 +213,9 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) { } func TestUpgradeRelease_ReuseValues(t *testing.T) { - is := assert.New(t) - t.Run("reuse values should work with values", func(t *testing.T) { + is := assert.New(t) + req := require.New(t) upAction := upgradeAction(t) existingValues := map[string]any{ @@ -240,31 +241,33 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { rel.Config = existingValues err := upAction.cfg.Releases.Create(rel) - is.NoError(err) + req.NoError(err) upAction.ReuseValues = true // setting newValues and upgrading resi, err := upAction.Run(rel.Name, buildChart(), newValues) - is.NoError(err) + req.NoError(err) res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) // Now make sure it is actually upgraded updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2) - is.NoError(err) + req.NoError(err) if updatedResi == nil { is.Fail("Updated Release is nil") return } updatedRes, err := releaserToV1Release(updatedResi) - is.NoError(err) + req.NoError(err) is.Equal(common.StatusDeployed, updatedRes.Info.Status) is.Equal(expectedValues, updatedRes.Config) }) t.Run("reuse values should not install disabled charts", func(t *testing.T) { + is := assert.New(t) + req := require.New(t) upAction := upgradeAction(t) chartDefaultValues := map[string]any{ "subchart": map[string]any{ @@ -301,7 +304,7 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { Version: 1, } err := upAction.cfg.Releases.Create(rel) - is.NoError(err) + req.NoError(err) upAction.ReuseValues = true sampleChartWithSubChart := buildChart( @@ -312,20 +315,20 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { ) // reusing values and upgrading resi, err := upAction.Run(rel.Name, sampleChartWithSubChart, map[string]any{}) - is.NoError(err) + req.NoError(err) res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) // Now get the upgraded release updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2) - is.NoError(err) + req.NoError(err) if updatedResi == nil { is.Fail("Updated Release is nil") return } updatedRes, err := releaserToV1Release(updatedResi) - is.NoError(err) + req.NoError(err) is.Equal(common.StatusDeployed, updatedRes.Info.Status) is.Empty(updatedRes.Chart.Dependencies(), "expected 0 dependencies") @@ -340,9 +343,9 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { } func TestUpgradeRelease_ResetThenReuseValues(t *testing.T) { - is := assert.New(t) - t.Run("reset then reuse values should work with values", func(t *testing.T) { + is := assert.New(t) + req := require.New(t) upAction := upgradeAction(t) existingValues := map[string]any{ @@ -371,25 +374,25 @@ func TestUpgradeRelease_ResetThenReuseValues(t *testing.T) { rel.Config = existingValues err := upAction.cfg.Releases.Create(rel) - is.NoError(err) + req.NoError(err) upAction.ResetThenReuseValues = true // setting newValues and upgrading resi, err := upAction.Run(rel.Name, buildChart(withValues(newChartValues)), newValues) - is.NoError(err) + req.NoError(err) res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) // Now make sure it is actually upgraded updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2) - is.NoError(err) + req.NoError(err) if updatedResi == nil { is.Fail("Updated Release is nil") return } updatedRes, err := releaserToV1Release(updatedResi) - is.NoError(err) + req.NoError(err) is.Equal(common.StatusDeployed, updatedRes.Info.Status) is.Equal(expectedValues, updatedRes.Config) @@ -440,9 +443,9 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { req.Error(err) res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.Contains(res.Info.Description, "Upgrade \"interrupted-release\" failed: context canceled") - is.Equal(res.Info.Status, common.StatusFailed) + is.Equal(common.StatusFailed, res.Info.Status) } func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) { @@ -453,7 +456,7 @@ func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) { rel := releaseStub() rel.Name = "interrupted-release" rel.Info.Status = common.StatusDeployed - require.NoError(t, upAction.cfg.Releases.Create(rel)) + req.NoError(upAction.cfg.Releases.Create(rel)) failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.WaitDuration = 5 * time.Second @@ -469,14 +472,14 @@ func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) { req.Error(err) is.Contains(err.Error(), "release interrupted-release failed, and has been rolled back due to rollback-on-failure being set: context canceled") res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) // Now make sure it is actually upgraded updatedResi, err := upAction.cfg.Releases.Get(res.Name, 3) - is.NoError(err) + req.NoError(err) updatedRes, err := releaserToV1Release(updatedResi) - is.NoError(err) + req.NoError(err) // Should have rolled back to the previous - is.Equal(updatedRes.Info.Status, common.StatusDeployed) + is.Equal(common.StatusDeployed, updatedRes.Info.Status) } func TestMergeCustomLabels(t *testing.T) { @@ -496,6 +499,7 @@ func TestMergeCustomLabels(t *testing.T) { func TestUpgradeRelease_Labels(t *testing.T) { is := assert.New(t) + req := require.New(t) upAction := upgradeAction(t) rel := releaseStub() @@ -508,7 +512,7 @@ func TestUpgradeRelease_Labels(t *testing.T) { rel.Info.Status = common.StatusDeployed err := upAction.cfg.Releases.Create(rel) - is.NoError(err) + req.NoError(err) upAction.Labels = map[string]string{ "key1": "null", @@ -517,39 +521,40 @@ func TestUpgradeRelease_Labels(t *testing.T) { } // setting newValues and upgrading resi, err := upAction.Run(rel.Name, buildChart(), nil) - is.NoError(err) + req.NoError(err) res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) // Now make sure it is actually upgraded and labels were merged updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2) - is.NoError(err) + req.NoError(err) if updatedResi == nil { is.Fail("Updated Release is nil") return } updatedRes, err := releaserToV1Release(updatedResi) - is.NoError(err) + req.NoError(err) is.Equal(common.StatusDeployed, updatedRes.Info.Status) is.Equal(mergeCustomLabels(rel.Labels, upAction.Labels), updatedRes.Labels) // Now make sure it is suppressed release still contains original labels initialResi, err := upAction.cfg.Releases.Get(res.Name, 1) - is.NoError(err) + req.NoError(err) if initialResi == nil { is.Fail("Updated Release is nil") return } initialRes, err := releaserToV1Release(initialResi) - is.NoError(err) - is.Equal(initialRes.Info.Status, common.StatusSuperseded) + req.NoError(err) + is.Equal(common.StatusSuperseded, initialRes.Info.Status) is.Equal(initialRes.Labels, rel.Labels) } func TestUpgradeRelease_SystemLabels(t *testing.T) { is := assert.New(t) + req := require.New(t) upAction := upgradeAction(t) rel := releaseStub() @@ -562,7 +567,7 @@ func TestUpgradeRelease_SystemLabels(t *testing.T) { rel.Info.Status = common.StatusDeployed err := upAction.cfg.Releases.Create(rel) - is.NoError(err) + req.NoError(err) upAction.Labels = map[string]string{ "key1": "null", @@ -596,7 +601,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) { done() req.NoError(err) res, err := releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.Equal(common.StatusPendingUpgrade, res.Info.Status) is.Contains(res.Manifest, "kind: Secret") @@ -604,7 +609,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) { req.NoError(err) lastRelease, err := releaserToV1Release(lastReleasei) req.NoError(err) - is.Equal(lastRelease.Info.Status, common.StatusDeployed) + is.Equal(common.StatusDeployed, lastRelease.Info.Status) is.Equal(1, lastRelease.Version) // Test the case for hiding the secret to ensure it is not displayed @@ -616,7 +621,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) { done() req.NoError(err) res, err = releaserToV1Release(resi) - is.NoError(err) + req.NoError(err) is.Equal(common.StatusPendingUpgrade, res.Info.Status) is.NotContains(res.Manifest, "kind: Secret") @@ -624,7 +629,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) { req.NoError(err) lastRelease, err = releaserToV1Release(lastReleasei) req.NoError(err) - is.Equal(lastRelease.Info.Status, common.StatusDeployed) + is.Equal(common.StatusDeployed, lastRelease.Info.Status) is.Equal(1, lastRelease.Version) // Ensure in a dry run mode when using HideSecret @@ -715,7 +720,7 @@ func TestGetUpgradeServerSideValue(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { serverSideApply, err := getUpgradeServerSideValue(tt.actionServerSideOption, tt.releaseApplyMethod) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.expectedServerSideApply, serverSideApply) }) } diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go index 3624d3676..75c29bf6b 100644 --- a/pkg/action/validate_test.go +++ b/pkg/action/validate_test.go @@ -25,6 +25,7 @@ import ( "helm.sh/helm/v4/pkg/kube" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/api/meta" @@ -130,7 +131,7 @@ func TestRequireAdoption(t *testing.T) { // Verify that a resource that lacks labels/annotations can be adopted found, err := requireAdoption(resources) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, found, 1) assert.Equal(t, found[0], existing) assert.NotSame(t, found[0], existing) @@ -155,7 +156,7 @@ func TestExistingResourceConflict(t *testing.T) { // Verify only existing resources are returned found, err := existingResourceConflict(resources, releaseName, releaseNamespace) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, found, 1) assert.Equal(t, found[0], existing) assert.NotSame(t, found[0], existing) @@ -171,21 +172,21 @@ func TestCheckOwnership(t *testing.T) { // Verify that a resource that lacks labels/annotations is not owned err := checkOwnership(deployFoo.Object, "rel-a", "ns-a") - assert.EqualError(t, err, `invalid ownership metadata; label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm"; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) + require.EqualError(t, err, `invalid ownership metadata; label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm"; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) // Set managed by label and verify annotation error message _ = accessor.SetLabels(deployFoo.Object, map[string]string{ appManagedByLabel: appManagedByHelm, }) err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") - assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) + require.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) // Set only the release name annotation and verify missing release namespace error message _ = accessor.SetAnnotations(deployFoo.Object, map[string]string{ helmReleaseNameAnnotation: "rel-a", }) err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") - assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) + require.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) // Set both release name and namespace annotations and verify no ownership errors _ = accessor.SetAnnotations(deployFoo.Object, map[string]string{ @@ -193,15 +194,15 @@ func TestCheckOwnership(t *testing.T) { helmReleaseNamespaceAnnotation: "ns-a", }) err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") - assert.NoError(t, err) + require.NoError(t, err) // Verify ownership error for wrong release name err = checkOwnership(deployFoo.Object, "rel-b", "ns-a") - assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-name" must equal "rel-b": current value is "rel-a"`) + require.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-name" must equal "rel-b": current value is "rel-a"`) // Verify ownership error for wrong release namespace err = checkOwnership(deployFoo.Object, "rel-a", "ns-b") - assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-namespace" must equal "ns-b": current value is "ns-a"`) + require.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-namespace" must equal "ns-b": current value is "ns-a"`) // Verify ownership error for wrong manager label _ = accessor.SetLabels(deployFoo.Object, map[string]string{ @@ -235,7 +236,7 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { resources := kube.ResourceList{owned1, owned2} ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, ownedList, 2) assert.Empty(t, unownedList) }) @@ -247,7 +248,7 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { resources := kube.ResourceList{owned, unowned} ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, ownedList, 1) assert.Len(t, unownedList, 1) assert.Equal(t, "owned", ownedList[0].Name) @@ -260,7 +261,7 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { resources := kube.ResourceList{missing} ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) - assert.NoError(t, err) + require.NoError(t, err) assert.Empty(t, ownedList) assert.Empty(t, unownedList) }) @@ -271,7 +272,7 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { resources := kube.ResourceList{noMeta} ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) - assert.NoError(t, err) + require.NoError(t, err) assert.Empty(t, ownedList) assert.Len(t, unownedList, 1) }) @@ -282,7 +283,7 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { resources := kube.ResourceList{otherRelease} ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) - assert.NoError(t, err) + require.NoError(t, err) assert.Empty(t, ownedList) assert.Len(t, unownedList, 1) }) @@ -295,7 +296,7 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { resources := kube.ResourceList{owned, unowned, missing} ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, ownedList, 1) assert.Len(t, unownedList, 1) assert.Equal(t, "owned", ownedList[0].Name) @@ -313,24 +314,24 @@ func TestSetMetadataVisitor(t *testing.T) { // Set release tracking metadata and verify no error err = resources.Visit(setMetadataVisitor("rel-a", "ns-a", true)) - assert.NoError(t, err) + require.NoError(t, err) // Verify that release "b" cannot take ownership of "a" err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) - assert.Error(t, err) + require.Error(t, err) // Force release "b" to take ownership err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", true)) - assert.NoError(t, err) + require.NoError(t, err) // Check that there is now no ownership error when setting metadata without force err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) - assert.NoError(t, err) + require.NoError(t, err) // Add a new resource that is missing ownership metadata and verify error resources.Append(newDeploymentResource("baz", "default", "")) err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), `Deployment "baz" in namespace "" cannot be owned`) } @@ -368,10 +369,10 @@ func TestValidateNameAndGenerateName(t *testing.T) { skip, err := validateNameAndGenerateName(tc.info) if tc.wantErr { - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tc.errContains) } else { - assert.NoError(t, err) + require.NoError(t, err) } assert.Equal(t, tc.wantSkip, skip) diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index 33274920c..d7f46a1da 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -25,6 +25,7 @@ import ( "text/template" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" @@ -734,6 +735,7 @@ func TestConcatPrefix(t *testing.T) { // from issue #31643 where chart has data: {} and user provides data: {foo: bar, baz: ~} func TestCoalesceValuesEmptyMapWithNils(t *testing.T) { is := assert.New(t) + req := require.New(t) c := &chart.Chart{ Metadata: &chart.Metadata{Name: "test"}, @@ -750,7 +752,7 @@ func TestCoalesceValuesEmptyMapWithNils(t *testing.T) { } v, err := CoalesceValues(c, vals) - is.NoError(err) + req.NoError(err) data, ok := v["data"].(map[string]any) is.True(ok, "data is not a map") @@ -769,6 +771,7 @@ func TestCoalesceValuesEmptyMapWithNils(t *testing.T) { // Regression test for issue #31919. func TestCoalesceValuesSubchartDefaultNilsCleaned(t *testing.T) { is := assert.New(t) + req := require.New(t) // Subchart has a default with nil values (e.g. keyMapping: {password: null}) subchart := &chart.Chart{ @@ -789,7 +792,7 @@ func TestCoalesceValuesSubchartDefaultNilsCleaned(t *testing.T) { vals := map[string]any{} v, err := CoalesceValues(parent, vals) - is.NoError(err) + req.NoError(err) childVals, ok := v["child"].(map[string]any) is.True(ok, "child values should be a map") @@ -807,6 +810,7 @@ func TestCoalesceValuesSubchartDefaultNilsCleaned(t *testing.T) { // Regression test for issue #31919. func TestCoalesceValuesUserNullErasesSubchartDefault(t *testing.T) { is := assert.New(t) + req := require.New(t) subchart := &chart.Chart{ Metadata: &chart.Metadata{Name: "child"}, @@ -828,7 +832,7 @@ func TestCoalesceValuesUserNullErasesSubchartDefault(t *testing.T) { } v, err := CoalesceValues(parent, vals) - is.NoError(err) + req.NoError(err) childVals, ok := v["child"].(map[string]any) is.True(ok, "child values should be a map") @@ -843,6 +847,7 @@ func TestCoalesceValuesUserNullErasesSubchartDefault(t *testing.T) { // Regression test for issue #31971. func TestCoalesceValuesSubchartNilDoesNotShadowGlobal(t *testing.T) { is := assert.New(t) + req := require.New(t) subchart := &chart.Chart{ Metadata: &chart.Metadata{Name: "child"}, @@ -868,7 +873,7 @@ func TestCoalesceValuesSubchartNilDoesNotShadowGlobal(t *testing.T) { } v, err := CoalesceValues(parent, vals) - is.NoError(err) + req.NoError(err) childVals, ok := v["child"].(map[string]any) is.True(ok, "child values should be a map") @@ -887,6 +892,7 @@ func TestCoalesceValuesSubchartNilDoesNotShadowGlobal(t *testing.T) { // the same map. Regression test for the coalesceTablesFullKey merge path. func TestCoalesceValuesSubchartNilCleanedWhenUserPartiallyOverrides(t *testing.T) { is := assert.New(t) + req := require.New(t) subchart := &chart.Chart{ Metadata: &chart.Metadata{Name: "child"}, @@ -913,7 +919,7 @@ func TestCoalesceValuesSubchartNilCleanedWhenUserPartiallyOverrides(t *testing.T } v, err := CoalesceValues(parent, vals) - is.NoError(err) + req.NoError(err) childVals, ok := v["child"].(map[string]any) is.True(ok, "child values should be a map") diff --git a/pkg/chart/v2/lint/lint_test.go b/pkg/chart/v2/lint/lint_test.go index 4256281e0..85960131b 100644 --- a/pkg/chart/v2/lint/lint_test.go +++ b/pkg/chart/v2/lint/lint_test.go @@ -22,6 +22,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/chart/v2/lint/support" chartutil "helm.sh/helm/v4/pkg/chart/v2/util" @@ -130,7 +131,7 @@ func TestBadCrdFile(t *testing.T) { var values map[string]any m := RunAll(badCrdFileDir, values, namespace).Messages assert.Lenf(t, m, 2, "All didn't fail with expected errors, got %#v", m) - assert.ErrorContains(t, m[0].Err, "apiVersion is not in 'apiextensions.k8s.io'") + require.ErrorContains(t, m[0].Err, "apiVersion is not in 'apiextensions.k8s.io'") assert.ErrorContains(t, m[1].Err, "object kind is not 'CustomResourceDefinition'") } diff --git a/pkg/cli/values/options_test.go b/pkg/cli/values/options_test.go index 498f2d3ba..c601de49e 100644 --- a/pkg/cli/values/options_test.go +++ b/pkg/cli/values/options_test.go @@ -224,7 +224,7 @@ func TestReadFile(t *testing.T) { assert.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) if tt.expectedData != nil { assert.Equal(t, tt.expectedData, got) diff --git a/pkg/cmd/helpers_test.go b/pkg/cmd/helpers_test.go index 37ceb6a85..3db5f38ab 100644 --- a/pkg/cmd/helpers_test.go +++ b/pkg/cmd/helpers_test.go @@ -286,9 +286,9 @@ func TestCmdGetDryRunFlagStrategy(t *testing.T) { t.Run(name, func(t *testing.T) { dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, tc.IsTemplate) if tc.ExpectedError { - assert.Error(t, err) + require.Error(t, err) } else { - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tc.ExpectedStrategy, dryRunStrategy) } diff --git a/pkg/cmd/history_test.go b/pkg/cmd/history_test.go index 3ec59a99f..b602168cb 100644 --- a/pkg/cmd/history_test.go +++ b/pkg/cmd/history_test.go @@ -451,7 +451,7 @@ func TestReleaseInfoEmptyStringRoundTrip(t *testing.T) { // Zero time value should be omitted assert.NotContains(t, result, "updated") - assert.Equal(t, float64(1), result["revision"]) + assert.InDelta(t, float64(1), result["revision"], 0.0001) assert.Equal(t, "deployed", result["status"]) assert.Equal(t, "mychart-1.0.0", result["chart"]) } diff --git a/pkg/downloader/cache_test.go b/pkg/downloader/cache_test.go index 7d7fb3059..d8a2c3768 100644 --- a/pkg/downloader/cache_test.go +++ b/pkg/downloader/cache_test.go @@ -94,7 +94,7 @@ func TestDiskCache_PutAndGet(t *testing.T) { // Get should return ErrNotExist for empty files _, err = cache.Get(emptyKey, CacheChart) - assert.ErrorIs(t, err, os.ErrNotExist, "Get for an empty file should return os.ErrNotExist") + require.ErrorIs(t, err, os.ErrNotExist, "Get for an empty file should return os.ErrNotExist") // But the file should exist _, err = os.Stat(path) diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index aea64a0b5..4924b6721 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -26,6 +26,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "sigs.k8s.io/yaml" chart "helm.sh/helm/v4/pkg/chart/v2" @@ -679,7 +680,7 @@ func TestDedupeRepos(t *testing.T) { func TestWriteLock(t *testing.T) { fixedTime, err := time.Parse(time.RFC3339, "2025-07-04T00:00:00Z") - assert.NoError(t, err) + require.NoError(t, err) lock := &chart.Lock{ Generated: fixedTime, Digest: "sha256:12345", @@ -692,76 +693,76 @@ func TestWriteLock(t *testing.T) { }, } expectedContent, err := yaml.Marshal(lock) - assert.NoError(t, err) + require.NoError(t, err) t.Run("v2 lock file", func(t *testing.T) { dir := t.TempDir() err := writeLock(dir, lock, false) - assert.NoError(t, err) + require.NoError(t, err) lockfilePath := filepath.Join(dir, "Chart.lock") _, err = os.Stat(lockfilePath) - assert.NoError(t, err, "Chart.lock should exist") + require.NoError(t, err, "Chart.lock should exist") content, err := os.ReadFile(lockfilePath) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, expectedContent, content) // Check that requirements.lock does not exist _, err = os.Stat(filepath.Join(dir, "requirements.lock")) - assert.Error(t, err) + require.Error(t, err) assert.True(t, os.IsNotExist(err)) }) t.Run("v1 lock file", func(t *testing.T) { dir := t.TempDir() err := writeLock(dir, lock, true) - assert.NoError(t, err) + require.NoError(t, err) lockfilePath := filepath.Join(dir, "requirements.lock") _, err = os.Stat(lockfilePath) - assert.NoError(t, err, "requirements.lock should exist") + require.NoError(t, err, "requirements.lock should exist") content, err := os.ReadFile(lockfilePath) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, expectedContent, content) // Check that Chart.lock does not exist _, err = os.Stat(filepath.Join(dir, "Chart.lock")) - assert.Error(t, err) + require.Error(t, err) assert.True(t, os.IsNotExist(err)) }) t.Run("overwrite existing lock file", func(t *testing.T) { dir := t.TempDir() lockfilePath := filepath.Join(dir, "Chart.lock") - assert.NoError(t, os.WriteFile(lockfilePath, []byte("old content"), 0o644)) + require.NoError(t, os.WriteFile(lockfilePath, []byte("old content"), 0o644)) err = writeLock(dir, lock, false) - assert.NoError(t, err) + require.NoError(t, err) content, err := os.ReadFile(lockfilePath) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, expectedContent, content) }) t.Run("lock file is a symlink", func(t *testing.T) { dir := t.TempDir() dummyFile := filepath.Join(dir, "dummy.txt") - assert.NoError(t, os.WriteFile(dummyFile, []byte("dummy"), 0o644)) + require.NoError(t, os.WriteFile(dummyFile, []byte("dummy"), 0o644)) lockfilePath := filepath.Join(dir, "Chart.lock") - assert.NoError(t, os.Symlink(dummyFile, lockfilePath)) + require.NoError(t, os.Symlink(dummyFile, lockfilePath)) err = writeLock(dir, lock, false) - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), "the Chart.lock file is a symlink to") }) t.Run("chart path is not a directory", func(t *testing.T) { dir := t.TempDir() filePath := filepath.Join(dir, "not-a-dir") - assert.NoError(t, os.WriteFile(filePath, []byte("file"), 0o644)) + require.NoError(t, os.WriteFile(filePath, []byte("file"), 0o644)) err = writeLock(filePath, lock, false) assert.Error(t, err) diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go index cf6a8d5c9..37f79478c 100644 --- a/pkg/engine/funcs_test.go +++ b/pkg/engine/funcs_test.go @@ -135,7 +135,7 @@ keyInElement1 = "valueInElement1"`, for _, tt := range tests { var b strings.Builder err := template.Must(template.New("test").Funcs(funcMap()).Parse(tt.tpl)).Execute(&b, tt.vars) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.expect, b.String(), tt.tpl) } @@ -187,7 +187,7 @@ keyInElement1 = "valueInElement1"`, var b strings.Builder err := template.Must(template.New("test").Funcs(funcMap()).Parse(tt.tpl)).Execute(&b, tt.vars) if tt.expect != nil { - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.expect, b.String(), tt.tpl) } else { assert.Error(t, err) @@ -475,7 +475,7 @@ func TestMerge(t *testing.T) { tpl := `{{merge .dst .src1 .src2}}` var b strings.Builder err := template.Must(template.New("test").Funcs(funcMap()).Parse(tpl)).Execute(&b, dict) - assert.NoError(t, err) + require.NoError(t, err) expected := map[string]any{ "a": "one", // key overridden diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 131695dc2..8a71e4b6b 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -968,7 +968,8 @@ func TestGetPodList(t *testing.T) { podList, err := c.GetPodList(namespace, metav1.ListOptions{}) clientAssertions := assert.New(t) - clientAssertions.NoError(err) + req := require.New(t) + req.NoError(err) podList.ResourceVersion = "" clientAssertions.Equal(&responsePodList, podList) } @@ -983,7 +984,8 @@ func TestOutputContainerLogsForPodList(t *testing.T) { outBufferFunc := func(_, _, _ string) io.Writer { return outBuffer } err := c.OutputContainerLogsForPodList(&somePodList, namespace, outBufferFunc) clientAssertions := assert.New(t) - clientAssertions.NoError(err) + req := require.New(t) + req.NoError(err) clientAssertions.Equal("fake logsfake logsfake logs", outBuffer.String()) } diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index 73a424720..12c44b420 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -273,7 +273,7 @@ func getRuntimeObjFromManifests(t *testing.T, manifests []string) []runtime.Obje for _, manifest := range manifests { m := make(map[string]any) err := yaml.Unmarshal([]byte(manifest), &m) - assert.NoError(t, err) + require.NoError(t, err) resource := &unstructured.Unstructured{Object: m} objects = append(objects, resource) } @@ -285,7 +285,7 @@ func getResourceListFromRuntimeObjs(t *testing.T, c *Client, objs []runtime.Obje resourceList := ResourceList{} for _, obj := range objs { list, err := c.Build(objBody(obj), false) - assert.NoError(t, err) + require.NoError(t, err) resourceList = append(resourceList, list...) } return resourceList @@ -333,7 +333,7 @@ func TestStatusWaitForDelete(t *testing.T) { u := objToCreate.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - assert.NoError(t, err) + require.NoError(t, err) } objsToDelete := getRuntimeObjFromManifests(t, tt.manifestsToDelete) for _, objToDelete := range objsToDelete { @@ -434,7 +434,7 @@ func TestStatusWait(t *testing.T) { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - assert.NoError(t, err) + require.NoError(t, err) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.Wait(resourceList, time.Second*3) @@ -491,7 +491,7 @@ func TestWaitForJobComplete(t *testing.T) { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - assert.NoError(t, err) + require.NoError(t, err) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.WaitWithJobs(resourceList, time.Second*3) @@ -554,7 +554,7 @@ func TestWatchForReady(t *testing.T) { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - assert.NoError(t, err) + require.NoError(t, err) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.WatchUntilReady(resourceList, time.Second*3) @@ -658,7 +658,7 @@ func TestStatusWaitMultipleNamespaces(t *testing.T) { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - assert.NoError(t, err) + require.NoError(t, err) } if strings.Contains(tt.name, "delete") { @@ -840,7 +840,7 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) err := baseFakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - assert.NoError(t, err) + require.NoError(t, err) } if strings.Contains(tt.name, "delet") { @@ -865,7 +865,7 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { } return } - assert.NoError(t, err) + require.NoError(t, err) assert.False(t, restrictedConfig.clusterScopedListAttempted) }) } @@ -948,7 +948,7 @@ func TestStatusWaitMixedResources(t *testing.T) { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) err := baseFakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - assert.NoError(t, err) + require.NoError(t, err) } if strings.Contains(tt.name, "delet") { @@ -973,7 +973,7 @@ func TestStatusWaitMixedResources(t *testing.T) { } return } - assert.NoError(t, err) + require.NoError(t, err) assert.False(t, restrictedConfig.clusterScopedListAttempted) }) } @@ -1068,7 +1068,7 @@ func TestStatusWaitWithCustomReaders(t *testing.T) { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - assert.NoError(t, err) + require.NoError(t, err) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.Wait(resourceList, time.Second*3) @@ -1140,7 +1140,7 @@ func TestStatusWaitWithJobsAndCustomReaders(t *testing.T) { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - assert.NoError(t, err) + require.NoError(t, err) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.WaitWithJobs(resourceList, time.Second*3) @@ -1239,7 +1239,7 @@ func TestStatusWaitWithFailedResources(t *testing.T) { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - assert.NoError(t, err) + require.NoError(t, err) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := tt.testFunc(&sw, resourceList, time.Second*3) @@ -1788,7 +1788,7 @@ func TestWatchUntilReadyWithCustomReaders(t *testing.T) { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - assert.NoError(t, err) + require.NoError(t, err) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.WatchUntilReady(resourceList, time.Second*3) diff --git a/pkg/kube/wait_test.go b/pkg/kube/wait_test.go index 73270b5a3..da730a22e 100644 --- a/pkg/kube/wait_test.go +++ b/pkg/kube/wait_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" appsv1beta1 "k8s.io/api/apps/v1beta1" appsv1beta2 "k8s.io/api/apps/v1beta2" @@ -227,10 +228,10 @@ func TestSelectorsForObject(t *testing.T) { t.Run(tt.name, func(t *testing.T) { selector, err := SelectorsForObject(tt.object.(runtime.Object)) if tt.expectError { - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.errorContains) } else { - assert.NoError(t, err) + require.NoError(t, err) expected := labels.Set(tt.expectedLabels) assert.True(t, selector.Matches(expected), "expected selector to match") } diff --git a/pkg/postrenderer/postrenderer_test.go b/pkg/postrenderer/postrenderer_test.go index 824a1d179..cea6f9f76 100644 --- a/pkg/postrenderer/postrenderer_test.go +++ b/pkg/postrenderer/postrenderer_test.go @@ -50,15 +50,16 @@ func TestNewPostRenderPluginWithOneArgsRun(t *testing.T) { t.Skip("skipping on windows") } is := assert.New(t) + req := require.New(t) s := cli.New() s.PluginsDirectory = "testdata/plugins" name := "postrenderer-v1" renderer, err := NewPostRendererPlugin(s, name, "ARG1") - require.NoError(t, err) + req.NoError(err) output, err := renderer.Run(bytes.NewBufferString("FOOTEST")) - is.NoError(err) + req.NoError(err) is.Contains(output.String(), "ARG1") } @@ -68,14 +69,15 @@ func TestNewPostRenderPluginWithTwoArgsRun(t *testing.T) { t.Skip("skipping on windows") } is := assert.New(t) + req := require.New(t) s := cli.New() s.PluginsDirectory = "testdata/plugins" name := "postrenderer-v1" renderer, err := NewPostRendererPlugin(s, name, "ARG1", "ARG2") - require.NoError(t, err) + req.NoError(err) output, err := renderer.Run(bytes.NewBufferString("FOOTEST")) - is.NoError(err) + req.NoError(err) is.Contains(output.String(), "ARG1 ARG2") } diff --git a/pkg/registry/chart_test.go b/pkg/registry/chart_test.go index 23a32506d..e27c3d619 100644 --- a/pkg/registry/chart_test.go +++ b/pkg/registry/chart_test.go @@ -237,7 +237,7 @@ func TestGenerateOCICreatedAnnotations(t *testing.T) { // Verify value of created artifact in RFC3339 format _, err := time.Parse(time.RFC3339, result[ocispec.AnnotationCreated]) - assert.NoError(t, err, "%s annotation with value '%s' not in RFC3339 format", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated]) + require.NoError(t, err, "%s annotation with value '%s' not in RFC3339 format", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated]) // Verify default creation time set result = generateOCIAnnotations(testChart, "") diff --git a/pkg/registry/client_scope_test.go b/pkg/registry/client_scope_test.go index f5f32ecb1..5f121f048 100644 --- a/pkg/registry/client_scope_test.go +++ b/pkg/registry/client_scope_test.go @@ -59,7 +59,7 @@ func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { }) lnCfg := net.ListenConfig{} listener, err := lnCfg.Listen(suite.T().Context(), "tcp", suite.AuthServerHost) - suite.NoError(err, "no error creating server listener") + suite.Require().NoError(err, "no error creating server listener") ts := httptest.NewUnstartedServer(handler) ts.Listener = listener @@ -69,18 +69,18 @@ func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { // basic push, good ref testingChartCreationTime := "1977-09-02T22:04:05Z" chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.NoError(err, "no error loading test chart") + suite.Require().NoError(err, "no error loading test chart") meta, err := extractChartMeta(chartData) - suite.NoError(err, "no error extracting chart meta") + suite.Require().NoError(err, "no error extracting chart meta") ref := fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) - suite.Error(err, "error pushing good ref because auth server doesn't give proper token") + suite.Require().Error(err, "error pushing good ref because auth server doesn't give proper token") //check the url that authentication server received select { case urlStr := <-requestURL: u, err := url.Parse(urlStr) - suite.NoError(err, "no error parsing requested URL") + suite.Require().NoError(err, "no error parsing requested URL") suite.Equal("/auth", u.Path) suite.Equal("testservice", u.Query().Get("service")) @@ -105,7 +105,7 @@ func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { }) lnCfg := net.ListenConfig{} listener, err := lnCfg.Listen(suite.T().Context(), "tcp", suite.AuthServerHost) - suite.NoError(err, "no error creating server listener") + suite.Require().NoError(err, "no error creating server listener") ts := httptest.NewUnstartedServer(handler) ts.Listener = listener @@ -115,18 +115,18 @@ func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { // Load test chart (to build ref pushed in previous test) // Simple pull, chart only chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.NoError(err, "no error loading test chart") + suite.Require().NoError(err, "no error loading test chart") meta, err := extractChartMeta(chartData) - suite.NoError(err, "no error extracting chart meta") + suite.Require().NoError(err, "no error extracting chart meta") ref := fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) _, err = suite.RegistryClient.Pull(ref) - suite.Error(err, "error pulling a simple chart because auth server doesn't give proper token") + suite.Require().Error(err, "error pulling a simple chart because auth server doesn't give proper token") //check the url that authentication server received select { case urlStr := <-requestURL: u, err := url.Parse(urlStr) - suite.NoError(err, "no error parsing requested URL") + suite.Require().NoError(err, "no error parsing requested URL") suite.Equal("/auth", u.Path) suite.Equal("testservice", u.Query().Get("service")) diff --git a/pkg/registry/reference_test.go b/pkg/registry/reference_test.go index f1a87fefc..07987cccb 100644 --- a/pkg/registry/reference_test.go +++ b/pkg/registry/reference_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func verify(t *testing.T, actual reference, registry, repository, tag, digest string) { @@ -45,30 +46,30 @@ func verify(t *testing.T, actual reference, registry, repository, tag, digest st func TestNewReference(t *testing.T) { actual, err := newReference("registry.example.com/repository:1.0@sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") - assert.NoError(t, err) + require.NoError(t, err) verify(t, actual, "registry.example.com", "repository", "1.0", "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") actual, err = newReference("oci://registry.example.com/repository:1.0@sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") - assert.NoError(t, err) + require.NoError(t, err) verify(t, actual, "registry.example.com", "repository", "1.0", "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") actual, err = newReference("a/b:1@c") - assert.NoError(t, err) + require.NoError(t, err) verify(t, actual, "a", "b", "1", "c") actual, err = newReference("a/b:@") - assert.NoError(t, err) + require.NoError(t, err) verify(t, actual, "a", "b", "", "") actual, err = newReference("registry.example.com/repository:1.0+001") - assert.NoError(t, err) + require.NoError(t, err) verify(t, actual, "registry.example.com", "repository", "1.0_001", "") actual, err = newReference("thing:1.0") - assert.Error(t, err) + require.Error(t, err) verify(t, actual, "", "", "", "") actual, err = newReference("registry.example.com/the/repository@sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") - assert.NoError(t, err) + require.NoError(t, err) verify(t, actual, "registry.example.com", "the/repository", "", "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888") } diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index a1cf11310..afcf88db5 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -560,9 +560,9 @@ func testPull(suite *TestRegistry) { suite.Equal( "sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256", result.Prov.Digest) - suite.Equal("{\"schemaVersion\":2,\"config\":{\"mediaType\":\"application/vnd.cncf.helm.config.v1+json\",\"digest\":\"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580\",\"size\":99},\"layers\":[{\"mediaType\":\"application/vnd.cncf.helm.chart.provenance.v1.prov\",\"digest\":\"sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256\",\"size\":695},{\"mediaType\":\"application/vnd.cncf.helm.chart.content.v1.tar+gzip\",\"digest\":\"sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55\",\"size\":973}],\"annotations\":{\"org.opencontainers.image.created\":\"1977-09-02T22:04:05Z\",\"org.opencontainers.image.description\":\"A Helm chart for Kubernetes\",\"org.opencontainers.image.title\":\"signtest\",\"org.opencontainers.image.version\":\"0.1.0\"}}", + suite.JSONEq("{\"schemaVersion\":2,\"config\":{\"mediaType\":\"application/vnd.cncf.helm.config.v1+json\",\"digest\":\"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580\",\"size\":99},\"layers\":[{\"mediaType\":\"application/vnd.cncf.helm.chart.provenance.v1.prov\",\"digest\":\"sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256\",\"size\":695},{\"mediaType\":\"application/vnd.cncf.helm.chart.content.v1.tar+gzip\",\"digest\":\"sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55\",\"size\":973}],\"annotations\":{\"org.opencontainers.image.created\":\"1977-09-02T22:04:05Z\",\"org.opencontainers.image.description\":\"A Helm chart for Kubernetes\",\"org.opencontainers.image.title\":\"signtest\",\"org.opencontainers.image.version\":\"0.1.0\"}}", string(result.Manifest.Data)) - suite.Equal("{\"name\":\"signtest\",\"version\":\"0.1.0\",\"description\":\"A Helm chart for Kubernetes\",\"apiVersion\":\"v1\"}", + suite.JSONEq("{\"name\":\"signtest\",\"version\":\"0.1.0\",\"description\":\"A Helm chart for Kubernetes\",\"apiVersion\":\"v1\"}", string(result.Config.Data)) suite.Equal(chartData, result.Chart.Data) suite.Equal(provData, result.Prov.Data) diff --git a/pkg/release/common_test.go b/pkg/release/common_test.go index bc22971e9..1adfcd979 100644 --- a/pkg/release/common_test.go +++ b/pkg/release/common_test.go @@ -21,6 +21,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" v2release "helm.sh/helm/v4/internal/release/v2" "helm.sh/helm/v4/pkg/release/common" @@ -31,6 +32,7 @@ func TestNewDefaultAccessor(t *testing.T) { // Testing the default implementation rather than NewAccessor which can be // overridden by developers. is := assert.New(t) + req := require.New(t) // Create release info := &rspb.Info{Status: common.StatusDeployed, LastDeployed: time.Now().Add(1000)} @@ -55,7 +57,7 @@ func TestNewDefaultAccessor(t *testing.T) { // can't be used with interfaces. The accessors enable access to the underlying data // in a manner that works with Go interfaces. accessor, err := newDefaultAccessor(rel) - is.NoError(err) + req.NoError(err) // Verify information is.Equal(rel.Name, accessor.Name()) @@ -68,6 +70,7 @@ func TestNewDefaultAccessor(t *testing.T) { func TestNewDefaultAccessorV2(t *testing.T) { // Testing the default implementation for v2 releases (charts/v3) is := assert.New(t) + req := require.New(t) // Create v2 release info := &v2release.Info{Status: common.StatusDeployed, LastDeployed: time.Now().Add(1000), Notes: "test notes"} @@ -93,7 +96,7 @@ func TestNewDefaultAccessorV2(t *testing.T) { // Test accessor creation accessor, err := newDefaultAccessor(rel) - is.NoError(err) + req.NoError(err) // Verify all accessor methods return correct values is.Equal(rel.Name, accessor.Name()) @@ -112,7 +115,7 @@ func TestNewDefaultAccessorV2(t *testing.T) { // Test hook accessor hookAccessor, err := newDefaultHookAccessor(hooks[0]) - is.NoError(err) + req.NoError(err) is.Equal("templates/hook.yaml", hookAccessor.Path()) is.Equal("hook manifest", hookAccessor.Manifest()) } @@ -120,6 +123,7 @@ func TestNewDefaultAccessorV2(t *testing.T) { func TestNewDefaultAccessorV2ByValue(t *testing.T) { // Test that passing v2 release by value also works is := assert.New(t) + req := require.New(t) info := &v2release.Info{Status: common.StatusDeployed, LastDeployed: time.Now()} rel := v2release.Release{ @@ -130,6 +134,6 @@ func TestNewDefaultAccessorV2ByValue(t *testing.T) { } accessor, err := newDefaultAccessor(rel) - is.NoError(err) + req.NoError(err) is.Equal("test-release", accessor.Name()) } diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go index 95f85faa6..f821e30d4 100644 --- a/pkg/storage/driver/memory_test.go +++ b/pkg/storage/driver/memory_test.go @@ -21,7 +21,7 @@ import ( "reflect" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release/common" @@ -296,7 +296,7 @@ func TestMemoryDelete(t *testing.T) { t.Errorf("expected end to be %d instead of %d", startLen-2, endLen) for _, ee := range end { rac, err := release.NewAccessor(ee) - assert.NoError(t, err, "unable to get release accessor") + require.NoError(t, err, "unable to get release accessor") t.Logf("Name: %s, Version: %d", rac.Name(), rac.Version()) } } diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index 9b4203224..ec57ae311 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -24,7 +24,7 @@ import ( "reflect" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release/common" @@ -113,7 +113,7 @@ func TestStorageDelete(t *testing.T) { } rhist, err := releaseListToV1List(hist) - assert.NoError(t, err) + require.NoError(t, err) // We have now deleted one of the two records. if len(rhist) != 1 { @@ -204,7 +204,7 @@ func TestStorageDeployed(t *testing.T) { } rel, err := releaserToV1Release(rls) - assert.NoError(t, err) + require.NoError(t, err) switch { case rls == nil: @@ -247,7 +247,7 @@ func TestStorageDeployedWithCorruption(t *testing.T) { } rel, err := releaserToV1Release(rls) - assert.NoError(t, err) + require.NoError(t, err) switch { case rls == nil: @@ -387,12 +387,10 @@ func TestStorageRemoveLeastRecent(t *testing.T) { // On inserting the 5th record, we expect two records to be pruned from history. hist, err := storage.History(name) - assert.NoError(t, err) + require.NoError(t, err) rhist, err := releaseListToV1List(hist) - assert.NoError(t, err) - if err != nil { - t.Fatal(err) - } else if len(rhist) != storage.MaxHistory { + require.NoError(t, err) + if len(rhist) != storage.MaxHistory { for _, item := range rhist { t.Logf("%s %v", item.Name, item.Version) } @@ -440,7 +438,7 @@ func TestStorageDoNotDeleteDeployed(t *testing.T) { t.Fatal(err) } else if len(hist) != storage.MaxHistory { rhist, err := releaseListToV1List(hist) - assert.NoError(t, err) + require.NoError(t, err) for _, item := range rhist { t.Logf("%s %v", item.Name, item.Version) } @@ -454,7 +452,7 @@ func TestStorageDoNotDeleteDeployed(t *testing.T) { } rhist, err := releaseListToV1List(hist) - assert.NoError(t, err) + require.NoError(t, err) for _, item := range rhist { if !expectedVersions[item.Version] { t.Errorf("Release version %d, found when not expected", item.Version) @@ -490,7 +488,7 @@ func TestStorageLast(t *testing.T) { } rel, err := releaserToV1Release(h) - assert.NoError(t, err) + require.NoError(t, err) if rel.Version != 4 { t.Errorf("Expected revision 4, got %d", rel.Version) @@ -544,7 +542,7 @@ func TestUpgradeInitiallyFailedReleaseWithHistoryLimit(t *testing.T) { } rhist, err := releaseListToV1List(hist) - assert.NoError(t, err) + require.NoError(t, err) for i, rel := range rhist { wantVersion := i + 2 if rel.Version != wantVersion { From 4ade4a5495b81a4cf465906a2414267ed0b1dcc8 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 6 Jul 2026 09:19:55 +0000 Subject: [PATCH 404/571] test: replace assert.Contains with assert.ErrorContains for better error handling Signed-off-by: Matthieu MOREL --- internal/chart/v3/lint/rules/values_test.go | 8 +--- internal/plugin/config_test.go | 3 +- internal/plugin/loader_test.go | 6 +-- internal/plugin/metadata_v1_test.go | 4 +- pkg/action/action_test.go | 22 ++++------- pkg/action/get_metadata_test.go | 6 +-- pkg/action/get_values_test.go | 6 +-- pkg/action/install_test.go | 5 +-- pkg/action/release_testing_test.go | 3 +- pkg/action/validate_test.go | 6 +-- pkg/chart/v2/lint/rules/values_test.go | 8 +--- pkg/cli/values/options_test.go | 3 +- pkg/downloader/manager_test.go | 3 +- pkg/kube/client_test.go | 25 +++++------- pkg/kube/statuswait_test.go | 44 +++++++++------------ pkg/kube/wait_test.go | 3 +- pkg/registry/tag_test.go | 10 ++--- 17 files changed, 59 insertions(+), 106 deletions(-) diff --git a/internal/chart/v3/lint/rules/values_test.go b/internal/chart/v3/lint/rules/values_test.go index c0d2f5299..36f5c403e 100644 --- a/internal/chart/v3/lint/rules/values_test.go +++ b/internal/chart/v3/lint/rules/values_test.go @@ -92,11 +92,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) { valfile := filepath.Join(tmpdir, "values.yaml") err := validateValuesFile(valfile, map[string]any{}, false) - if err == nil { - t.Fatal("expected values file to fail parsing") - } - - assert.Contains(t, err.Error(), "- at '/username': got number, want string") + assert.ErrorContains(t, err, "- at '/username': got number, want string") } func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T) { @@ -167,7 +163,7 @@ func TestValidateValuesFile(t *testing.T) { case err == nil && tt.errorMessage != "": t.Error("expected values file to fail parsing") case err != nil && tt.errorMessage != "": - assert.Contains(t, err.Error(), tt.errorMessage, "Failed with unexpected error") + assert.ErrorContains(t, err, tt.errorMessage, "Failed with unexpected error") } }) } diff --git a/internal/plugin/config_test.go b/internal/plugin/config_test.go index beac05abf..ec81ffed6 100644 --- a/internal/plugin/config_test.go +++ b/internal/plugin/config_test.go @@ -49,8 +49,7 @@ func TestUnmarshaConfig(t *testing.T) { config, err := unmarshalConfig("cli/v1", map[string]any{ "invalid field": "foo", }) - require.Error(t, err) - assert.Contains(t, err.Error(), "field not found") + require.ErrorContains(t, err, "field not found") assert.Nil(t, config) } } diff --git a/internal/plugin/loader_test.go b/internal/plugin/loader_test.go index cb0de968d..4b98a6f83 100644 --- a/internal/plugin/loader_test.go +++ b/internal/plugin/loader_test.go @@ -295,8 +295,7 @@ command: echo test`, m, err := loadMetadataLegacy([]byte(tc.yaml)) if tc.expectError { - require.Error(t, err) - assert.Contains(t, err.Error(), tc.errorContains) + require.ErrorContains(t, err, tc.errorContains) t.Logf("Legacy error (validation catches empty name): %v", err) if tc.logNote != "" { t.Log(tc.logNote) @@ -342,8 +341,7 @@ runtime: subprocess m, err := loadMetadataV1([]byte(tc.yaml)) if tc.expectError { - require.Error(t, err) - assert.Contains(t, err.Error(), tc.errorContains) + require.ErrorContains(t, err, tc.errorContains) t.Logf("V1 error (strict unmarshalling): %v", err) } else { require.NoError(t, err) diff --git a/internal/plugin/metadata_v1_test.go b/internal/plugin/metadata_v1_test.go index 21a78549e..74c67178e 100644 --- a/internal/plugin/metadata_v1_test.go +++ b/internal/plugin/metadata_v1_test.go @@ -19,7 +19,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestMetadataV1ValidateVersion(t *testing.T) { @@ -79,8 +78,7 @@ func TestMetadataV1ValidateVersion(t *testing.T) { m := base() m.Version = tc.version err := m.Validate() - require.Error(t, err) - assert.Contains(t, err.Error(), tc.errMsg) + assert.ErrorContains(t, err, tc.errMsg) }) } } diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index 045fa7d1e..4ad08b761 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -358,8 +358,7 @@ func TestConfiguration_Init(t *testing.T) { actualErr := cfg.Init(nil, "default", tt.helmDriver) if tt.expectErr { - require.Error(t, actualErr) - assert.Contains(t, actualErr.Error(), tt.errMsg) + require.ErrorContains(t, actualErr, tt.errMsg) } else { require.NoError(t, actualErr) assert.IsType(t, tt.expectedDriverType, cfg.Releases.Driver) @@ -1575,8 +1574,7 @@ data: merged, err := annotateAndMerge(tt.files) if tt.expectedError != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.expectedError) + assert.ErrorContains(t, err, tt.expectedError) } else { require.NoError(t, err) assert.NotNil(t, merged) @@ -1738,8 +1736,7 @@ data: files, err := splitAndDeannotate(tt.input, "test") if tt.expectedError != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.expectedError) + require.ErrorContains(t, err, tt.expectedError) } else { require.NoError(t, err) assert.Len(t, files, len(tt.expectedFiles)) @@ -1874,8 +1871,7 @@ func TestRenderResources_PostRenderer_Error(t *testing.T) { mockPR, false, false, false, PostRenderStrategyCombined, ) - require.Error(t, err) - assert.Contains(t, err.Error(), "error while running post render on files") + assert.ErrorContains(t, err, "error while running post render on files") } func TestRenderResources_PostRenderer_MergeError(t *testing.T) { @@ -1903,7 +1899,7 @@ func TestRenderResources_PostRenderer_MergeError(t *testing.T) { ) require.Error(t, err) - assert.Contains(t, err.Error(), "error merging manifests") + assert.ErrorContains(t, err, "error merging manifests") } func TestRenderResources_PostRenderer_SplitError(t *testing.T) { @@ -1924,8 +1920,7 @@ func TestRenderResources_PostRenderer_SplitError(t *testing.T) { mockPR, false, false, false, PostRenderStrategyCombined, ) - require.Error(t, err) - assert.Contains(t, err.Error(), "error while parsing post rendered output: error parsing YAML: MalformedYAMLError:") + assert.ErrorContains(t, err, "error while parsing post rendered output: error parsing YAML: MalformedYAMLError:") } func TestRenderResources_PostRenderer_Integration(t *testing.T) { @@ -2284,9 +2279,8 @@ metadata: mockPR, false, false, false, PostRenderStrategy("bogus"), ) - require.Error(t, err) - assert.Contains(t, err.Error(), "unknown post-render strategy") - assert.Contains(t, err.Error(), "bogus") + require.ErrorContains(t, err, "unknown post-render strategy") + assert.ErrorContains(t, err, "bogus") } func TestDetermineReleaseSSAApplyMethod(t *testing.T) { diff --git a/pkg/action/get_metadata_test.go b/pkg/action/get_metadata_test.go index ee0bd84a0..37f20fb60 100644 --- a/pkg/action/get_metadata_test.go +++ b/pkg/action/get_metadata_test.go @@ -443,8 +443,7 @@ func TestGetMetadata_Run_UnreachableKubeClient(t *testing.T) { client := NewGetMetadata(cfg) _, err := client.Run("test-release") - require.Error(t, err) - assert.Contains(t, err.Error(), "connection refused") + assert.ErrorContains(t, err, "connection refused") } func TestGetMetadata_Run_ReleaseNotFound(t *testing.T) { @@ -452,8 +451,7 @@ func TestGetMetadata_Run_ReleaseNotFound(t *testing.T) { client := NewGetMetadata(cfg) _, err := client.Run("non-existent-release") - require.Error(t, err) - assert.Contains(t, err.Error(), "not found") + assert.ErrorContains(t, err, "not found") } func TestGetMetadata_Run_EmptyAppVersion(t *testing.T) { diff --git a/pkg/action/get_values_test.go b/pkg/action/get_values_test.go index ce937b234..7e455baee 100644 --- a/pkg/action/get_values_test.go +++ b/pkg/action/get_values_test.go @@ -177,8 +177,7 @@ func TestGetValues_Run_UnreachableKubeClient(t *testing.T) { client := NewGetValues(cfg) _, err := client.Run("test-release") - require.Error(t, err) - assert.Contains(t, err.Error(), "connection refused") + assert.ErrorContains(t, err, "connection refused") } func TestGetValues_Run_ReleaseNotFound(t *testing.T) { @@ -186,8 +185,7 @@ func TestGetValues_Run_ReleaseNotFound(t *testing.T) { client := NewGetValues(cfg) _, err := client.Run("non-existent-release") - require.Error(t, err) - assert.Contains(t, err.Error(), "not found") + assert.ErrorContains(t, err, "not found") } func TestGetValues_Run_NilConfig(t *testing.T) { diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 28f989524..0d0e58e4a 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -334,10 +334,7 @@ func TestInstallRelease_NoName(t *testing.T) { instAction.ReleaseName = "" vals := map[string]any{} _, err := instAction.Run(buildChart(), vals) - if err == nil { - t.Fatal("expected failure when no name is specified") - } - assert.Contains(t, err.Error(), "no name provided") + assert.ErrorContains(t, err, "no name provided") } func TestInstallRelease_WithNotes(t *testing.T) { diff --git a/pkg/action/release_testing_test.go b/pkg/action/release_testing_test.go index d2a365cba..48a3251a1 100644 --- a/pkg/action/release_testing_test.go +++ b/pkg/action/release_testing_test.go @@ -231,8 +231,7 @@ func TestGetContainerLogs_PodNotFound(t *testing.T) { var buf bytes.Buffer err := rt.getContainerLogs(&buf, client, "nonexistent-pod") - require.Error(t, err) - assert.Contains(t, err.Error(), "unable to get pod nonexistent-pod") + assert.ErrorContains(t, err, "unable to get pod nonexistent-pod") } func TestGetContainerLogs_OutputHeaderFormat(t *testing.T) { diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go index 75c29bf6b..878bec599 100644 --- a/pkg/action/validate_test.go +++ b/pkg/action/validate_test.go @@ -331,8 +331,7 @@ func TestSetMetadataVisitor(t *testing.T) { // Add a new resource that is missing ownership metadata and verify error resources.Append(newDeploymentResource("baz", "default", "")) err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) - require.Error(t, err) - assert.Contains(t, err.Error(), `Deployment "baz" in namespace "" cannot be owned`) + assert.ErrorContains(t, err, `Deployment "baz" in namespace "" cannot be owned`) } func TestValidateNameAndGenerateName(t *testing.T) { @@ -369,8 +368,7 @@ func TestValidateNameAndGenerateName(t *testing.T) { skip, err := validateNameAndGenerateName(tc.info) if tc.wantErr { - require.Error(t, err) - assert.Contains(t, err.Error(), tc.errContains) + require.ErrorContains(t, err, tc.errContains) } else { require.NoError(t, err) } diff --git a/pkg/chart/v2/lint/rules/values_test.go b/pkg/chart/v2/lint/rules/values_test.go index c0d2f5299..36f5c403e 100644 --- a/pkg/chart/v2/lint/rules/values_test.go +++ b/pkg/chart/v2/lint/rules/values_test.go @@ -92,11 +92,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) { valfile := filepath.Join(tmpdir, "values.yaml") err := validateValuesFile(valfile, map[string]any{}, false) - if err == nil { - t.Fatal("expected values file to fail parsing") - } - - assert.Contains(t, err.Error(), "- at '/username': got number, want string") + assert.ErrorContains(t, err, "- at '/username': got number, want string") } func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T) { @@ -167,7 +163,7 @@ func TestValidateValuesFile(t *testing.T) { case err == nil && tt.errorMessage != "": t.Error("expected values file to fail parsing") case err != nil && tt.errorMessage != "": - assert.Contains(t, err.Error(), tt.errorMessage, "Failed with unexpected error") + assert.ErrorContains(t, err, tt.errorMessage, "Failed with unexpected error") } }) } diff --git a/pkg/cli/values/options_test.go b/pkg/cli/values/options_test.go index c601de49e..40590f33f 100644 --- a/pkg/cli/values/options_test.go +++ b/pkg/cli/values/options_test.go @@ -258,8 +258,7 @@ func TestReadFileErrorMessages(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := readFile(tt.filePath, tt.providers) - require.Error(t, err) - assert.Contains(t, err.Error(), tt.wantErr) + require.ErrorContains(t, err, tt.wantErr) }) } } diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index 4924b6721..e0cea0cd2 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -755,8 +755,7 @@ func TestWriteLock(t *testing.T) { require.NoError(t, os.Symlink(dummyFile, lockfilePath)) err = writeLock(dir, lock, false) - require.Error(t, err) - assert.Contains(t, err.Error(), "the Chart.lock file is a symlink to") + assert.ErrorContains(t, err, "the Chart.lock file is a symlink to") }) t.Run("chart path is not a directory", func(t *testing.T) { diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 8a71e4b6b..4a28fc5ed 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -539,8 +539,7 @@ func TestUpdate(t *testing.T) { ClientUpdateOptionUpgradeClientSideFieldManager(true)) if tc.ExpectedError != "" { - require.Error(t, err) - require.Contains(t, err.Error(), tc.ExpectedError) + require.ErrorContains(t, err, tc.ExpectedError) } else { require.NoError(t, err) } @@ -558,7 +557,7 @@ func TestUpdate(t *testing.T) { if tc.ExpectedError != "" { require.Error(t, err) - require.Contains(t, err.Error(), tc.ExpectedError) + require.ErrorContains(t, err, tc.ExpectedError) } else { require.NoError(t, err) } @@ -1939,7 +1938,7 @@ func TestClientWaitContextCancellationLegacy(t *testing.T) { err = c.Wait(resources, time.Second*30) require.Error(t, err) - assert.Contains(t, err.Error(), "context canceled", "expected context canceled error, got: %v", err) + assert.ErrorContains(t, err, "context canceled", "expected context canceled error, got: %v", err) } func TestClientWaitWithJobsContextCancellationLegacy(t *testing.T) { @@ -1993,8 +1992,7 @@ func TestClientWaitWithJobsContextCancellationLegacy(t *testing.T) { assert.Len(t, result.Created, 1, "expected 1 resource created, got %d", len(result.Created)) err = c.WaitWithJobs(resources, time.Second*30) - require.Error(t, err) - assert.Contains(t, err.Error(), "context canceled", "expected context canceled error, got: %v", err) + assert.ErrorContains(t, err, "context canceled", "expected context canceled error, got: %v", err) } func TestClientWaitForDeleteContextCancellationLegacy(t *testing.T) { @@ -2058,8 +2056,7 @@ func TestClientWaitForDeleteContextCancellationLegacy(t *testing.T) { } err = c.WaitForDelete(resources, time.Second*30) - require.Error(t, err) - assert.Contains(t, err.Error(), "context canceled", "expected context canceled error, got: %v", err) + assert.ErrorContains(t, err, "context canceled", "expected context canceled error, got: %v", err) } func TestClientWaitContextNilDoesNotPanic(t *testing.T) { @@ -2166,8 +2163,7 @@ func TestClientWaitContextPreCancelledLegacy(t *testing.T) { assert.Len(t, result.Created, 1, "expected 1 resource created, got %d", len(result.Created)) err = c.Wait(resources, time.Second*30) - require.Error(t, err) - assert.Contains(t, err.Error(), "context canceled", "expected context canceled error, got: %v", err) + assert.ErrorContains(t, err, "context canceled", "expected context canceled error, got: %v", err) } func TestClientWaitContextCancellationStatusWatcher(t *testing.T) { @@ -2193,8 +2189,7 @@ metadata: cancel() err = c.Wait(resources, time.Second*30) - require.Error(t, err) - assert.Contains(t, err.Error(), "context canceled", "expected context canceled error, got: %v", err) + assert.ErrorContains(t, err, "context canceled", "expected context canceled error, got: %v", err) } func TestClientWaitWithJobsContextCancellationStatusWatcher(t *testing.T) { @@ -2220,8 +2215,7 @@ metadata: cancel() err = c.WaitWithJobs(resources, time.Second*30) - require.Error(t, err) - assert.Contains(t, err.Error(), "context canceled", "expected context canceled error, got: %v", err) + assert.ErrorContains(t, err, "context canceled", "expected context canceled error, got: %v", err) } func TestClientWaitForDeleteContextCancellationStatusWatcher(t *testing.T) { @@ -2252,8 +2246,7 @@ status: cancel() err = c.WaitForDelete(resources, time.Second*30) - require.Error(t, err) - assert.Contains(t, err.Error(), "context canceled", "expected context canceled error, got: %v", err) + assert.ErrorContains(t, err, "context canceled", "expected context canceled error, got: %v", err) } // testStatusReader is a custom status reader for testing that returns a configurable status. diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index 12c44b420..14f10124c 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -350,7 +350,7 @@ func TestStatusWaitForDelete(t *testing.T) { if tt.expectErrs != nil { require.Error(t, err) for _, expectedErrStr := range tt.expectErrs { - assert.Contains(t, err.Error(), expectedErrStr) + require.ErrorContains(t, err, expectedErrStr) } return } @@ -441,7 +441,7 @@ func TestStatusWait(t *testing.T) { if tt.expectErrStrs != nil { require.Error(t, err) for _, expectedErrStr := range tt.expectErrStrs { - assert.Contains(t, err.Error(), expectedErrStr) + require.ErrorContains(t, err, expectedErrStr) } return } @@ -498,7 +498,7 @@ func TestWaitForJobComplete(t *testing.T) { if tt.expectErrStrs != nil { require.Error(t, err) for _, expectedErrStr := range tt.expectErrStrs { - assert.Contains(t, err.Error(), expectedErrStr) + require.ErrorContains(t, err, expectedErrStr) } return } @@ -561,7 +561,7 @@ func TestWatchForReady(t *testing.T) { if tt.expectErrStrs != nil { require.Error(t, err) for _, expectedErrStr := range tt.expectErrStrs { - assert.Contains(t, err.Error(), expectedErrStr) + require.ErrorContains(t, err, expectedErrStr) } return } @@ -679,7 +679,7 @@ func TestStatusWaitMultipleNamespaces(t *testing.T) { if tt.expectErrStrs != nil { require.Error(t, err) for _, expectedErrStr := range tt.expectErrStrs { - assert.Contains(t, err.Error(), expectedErrStr) + require.ErrorContains(t, err, expectedErrStr) } return } @@ -861,7 +861,7 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { if tt.expectErrs != nil { require.Error(t, err) for _, expectedErr := range tt.expectErrs { - assert.Contains(t, err.Error(), expectedErr.Error()) + require.ErrorContains(t, err, expectedErr.Error()) } return } @@ -969,7 +969,7 @@ func TestStatusWaitMixedResources(t *testing.T) { if tt.expectErrs != nil { require.Error(t, err) for _, expectedErr := range tt.expectErrs { - assert.Contains(t, err.Error(), expectedErr.Error()) + require.ErrorContains(t, err, expectedErr.Error()) } return } @@ -1075,7 +1075,7 @@ func TestStatusWaitWithCustomReaders(t *testing.T) { if tt.expectErrStrs != nil { require.Error(t, err) for _, expectedErrStr := range tt.expectErrStrs { - assert.Contains(t, err.Error(), expectedErrStr) + require.ErrorContains(t, err, expectedErrStr) } return } @@ -1246,7 +1246,7 @@ func TestStatusWaitWithFailedResources(t *testing.T) { if tt.expectErrStrs != nil { require.Error(t, err) for _, expectedErrStr := range tt.expectErrStrs { - assert.Contains(t, err.Error(), expectedErrStr) + require.ErrorContains(t, err, expectedErrStr) } return } @@ -1328,8 +1328,7 @@ func TestMethodSpecificContextCancellation(t *testing.T) { err := sw.WatchUntilReady(resourceList, time.Second*3) // Should fail due to cancelled method context - require.Error(t, err) - assert.Contains(t, err.Error(), "context canceled") + assert.ErrorContains(t, err, "context canceled") }) t.Run("Wait uses method-specific context", func(t *testing.T) { @@ -1362,8 +1361,7 @@ func TestMethodSpecificContextCancellation(t *testing.T) { err := sw.Wait(resourceList, time.Second*3) // Should fail due to cancelled method context - require.Error(t, err) - assert.Contains(t, err.Error(), "context canceled") + assert.ErrorContains(t, err, "context canceled") }) t.Run("WaitWithJobs uses method-specific context", func(t *testing.T) { @@ -1396,8 +1394,7 @@ func TestMethodSpecificContextCancellation(t *testing.T) { err := sw.WaitWithJobs(resourceList, time.Second*3) // Should fail due to cancelled method context - require.Error(t, err) - assert.Contains(t, err.Error(), "context canceled") + assert.ErrorContains(t, err, "context canceled") }) t.Run("WaitForDelete uses method-specific context", func(t *testing.T) { @@ -1430,8 +1427,7 @@ func TestMethodSpecificContextCancellation(t *testing.T) { err := sw.WaitForDelete(resourceList, time.Second*3) // Should fail due to cancelled method context - require.Error(t, err) - assert.Contains(t, err.Error(), "context canceled") + assert.ErrorContains(t, err, "context canceled") }) } @@ -1468,8 +1464,7 @@ func TestMethodContextFallbackToGeneralContext(t *testing.T) { err := sw.WatchUntilReady(resourceList, time.Second*3) // Should fail due to cancelled general context - require.Error(t, err) - assert.Contains(t, err.Error(), "context canceled") + assert.ErrorContains(t, err, "context canceled") }) t.Run("Wait falls back to general context when method context is nil", func(t *testing.T) { @@ -1502,8 +1497,7 @@ func TestMethodContextFallbackToGeneralContext(t *testing.T) { err := sw.Wait(resourceList, time.Second*3) // Should fail due to cancelled general context - require.Error(t, err) - assert.Contains(t, err.Error(), "context canceled") + assert.ErrorContains(t, err, "context canceled") }) t.Run("WaitWithJobs falls back to general context when method context is nil", func(t *testing.T) { @@ -1536,8 +1530,7 @@ func TestMethodContextFallbackToGeneralContext(t *testing.T) { err := sw.WaitWithJobs(resourceList, time.Second*3) // Should fail due to cancelled general context - require.Error(t, err) - assert.Contains(t, err.Error(), "context canceled") + assert.ErrorContains(t, err, "context canceled") }) t.Run("WaitForDelete falls back to general context when method context is nil", func(t *testing.T) { @@ -1570,8 +1563,7 @@ func TestMethodContextFallbackToGeneralContext(t *testing.T) { err := sw.WaitForDelete(resourceList, time.Second*3) // Should fail due to cancelled general context - require.Error(t, err) - assert.Contains(t, err.Error(), "context canceled") + assert.ErrorContains(t, err, "context canceled") }) } @@ -1795,7 +1787,7 @@ func TestWatchUntilReadyWithCustomReaders(t *testing.T) { if tt.expectErrStrs != nil { require.Error(t, err) for _, expectedErrStr := range tt.expectErrStrs { - assert.Contains(t, err.Error(), expectedErrStr) + require.ErrorContains(t, err, expectedErrStr) } return } diff --git a/pkg/kube/wait_test.go b/pkg/kube/wait_test.go index da730a22e..acdfffa23 100644 --- a/pkg/kube/wait_test.go +++ b/pkg/kube/wait_test.go @@ -228,8 +228,7 @@ func TestSelectorsForObject(t *testing.T) { t.Run(tt.name, func(t *testing.T) { selector, err := SelectorsForObject(tt.object.(runtime.Object)) if tt.expectError { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.errorContains) + require.ErrorContains(t, err, tt.errorContains) } else { require.NoError(t, err) expected := labels.Set(tt.expectedLabels) diff --git a/pkg/registry/tag_test.go b/pkg/registry/tag_test.go index 830eac7e1..e6f447eb3 100644 --- a/pkg/registry/tag_test.go +++ b/pkg/registry/tag_test.go @@ -19,6 +19,7 @@ package registry import ( "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -61,15 +62,14 @@ func TestGetTagMatchingVersionOrConstraint_InvalidConstraint(t *testing.T) { func TestGetTagMatchingVersionOrConstraint_NoMatches(t *testing.T) { tags := []string{"0.1.0", "0.2.0"} _, err := GetTagMatchingVersionOrConstraint(tags, ">=1.0.0") - require.Error(t, err, "expected error when no tags match") - require.Contains(t, err.Error(), ">=1.0.0", "expected error to contain version string") + assert.ErrorContains(t, err, ">=1.0.0", "expected error to contain version string") } func TestGetTagMatchingVersionOrConstraint_SkipsNonSemverTags(t *testing.T) { tags := []string{"alpha", "1.0.0", "beta", "1.1.0"} got, err := GetTagMatchingVersionOrConstraint(tags, ">=1.0.0 <2.0.0") require.NoError(t, err) - require.Equal(t, "1.0.0", got) + assert.Equal(t, "1.0.0", got) } func TestGetTagMatchingVersionOrConstraint_OrderMatters_FirstMatchReturned(t *testing.T) { @@ -77,7 +77,7 @@ func TestGetTagMatchingVersionOrConstraint_OrderMatters_FirstMatchReturned(t *te tags := []string{"1.3.0", "1.2.0"} got, err := GetTagMatchingVersionOrConstraint(tags, ">=1.2.0 <2.0.0") require.NoError(t, err) - require.Equal(t, "1.3.0", got, "first satisfying tag") + assert.Equal(t, "1.3.0", got, "first satisfying tag") } func TestGetTagMatchingVersionOrConstraint_ExactMatchHasPrecedence(t *testing.T) { @@ -85,5 +85,5 @@ func TestGetTagMatchingVersionOrConstraint_ExactMatchHasPrecedence(t *testing.T) tags := []string{"1.3.0", "1.2.3"} got, err := GetTagMatchingVersionOrConstraint(tags, "1.2.3") require.NoError(t, err) - require.Equal(t, "1.2.3", got, "expected exact match") + assert.Equal(t, "1.2.3", got, "expected exact match") } From 65077c10fa3e4f339142f498d84c1c7bfb1d320b Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 6 Jul 2026 09:37:12 +0000 Subject: [PATCH 405/571] test: replace assert.Equal with require.EqualError for improved error handling Signed-off-by: Matthieu MOREL --- pkg/strvals/literal_parser_test.go | 8 ++------ pkg/strvals/parser_test.go | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/pkg/strvals/literal_parser_test.go b/pkg/strvals/literal_parser_test.go index 5d65557c5..aab8e1b73 100644 --- a/pkg/strvals/literal_parser_test.go +++ b/pkg/strvals/literal_parser_test.go @@ -451,9 +451,7 @@ func TestParseLiteralNestedLevels(t *testing.T) { if err != nil { if tt.err { if tt.errStr != "" { - if err.Error() != tt.errStr { - assert.Equal(t, tt.errStr, err.Error()) - } + require.EqualError(t, err, tt.errStr) } continue } @@ -468,9 +466,7 @@ func TestParseLiteralNestedLevels(t *testing.T) { require.NoError(t, err) y2, err := yaml.Marshal(got) - if err != nil { - require.NoError(t, err, "Error serializing parsed value") - } + require.NoError(t, err, "Error serializing parsed value") if !bytes.Equal(y1, y2) { assert.Equal(t, string(y1), string(y2), tt.str) diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go index 6d254ca8d..1b73ae16a 100644 --- a/pkg/strvals/parser_test.go +++ b/pkg/strvals/parser_test.go @@ -779,9 +779,7 @@ func TestParseSetNestedLevels(t *testing.T) { if err != nil { if tt.err { if tt.errStr != "" { - if err.Error() != tt.errStr { - assert.Equal(t, tt.errStr, err.Error()) - } + require.EqualError(t, err, tt.errStr) } continue } @@ -794,9 +792,7 @@ func TestParseSetNestedLevels(t *testing.T) { y1, err := yaml.Marshal(tt.expect) require.NoError(t, err) y2, err := yaml.Marshal(got) - if err != nil { - require.NoError(t, err, "Error serializing parsed value") - } + require.NoError(t, err, "Error serializing parsed value") if !bytes.Equal(y1, y2) { assert.Equal(t, string(y1), string(y2), tt.str) From c1569ea83ff40ab534f3db531316cb0408717d3c Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 6 Jul 2026 17:27:08 +0200 Subject: [PATCH 406/571] Update pkg/cmd/history_test.go Co-authored-by: George Jenkins Signed-off-by: Matthieu MOREL --- pkg/cmd/history_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/history_test.go b/pkg/cmd/history_test.go index b602168cb..2bace98e0 100644 --- a/pkg/cmd/history_test.go +++ b/pkg/cmd/history_test.go @@ -451,7 +451,7 @@ func TestReleaseInfoEmptyStringRoundTrip(t *testing.T) { // Zero time value should be omitted assert.NotContains(t, result, "updated") - assert.InDelta(t, float64(1), result["revision"], 0.0001) + assert.InDelta(t, float64(1), result["revision"], 0.0) assert.Equal(t, "deployed", result["status"]) assert.Equal(t, "mychart-1.0.0", result["chart"]) } From 96b315f188932b7602e1ab1497e2d81d1caa8068 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 6 Jul 2026 23:16:46 +0200 Subject: [PATCH 407/571] chore: fix emptyStringTest, nestingReduce and singleCaseSwitch checks issues from gocritic Signed-off-by: Matthieu MOREL --- .golangci.yml | 3 - internal/chart/v3/lint/rules/chartfile.go | 2 +- internal/chart/v3/util/dependencies.go | 2 +- .../plugin/installer/http_installer_test.go | 2 +- internal/plugin/metadata.go | 2 +- internal/plugin/metadata_legacy.go | 2 +- internal/plugin/subprocess_commands.go | 4 +- .../release/v2/util/manifest_sorter_test.go | 37 ++++---- pkg/action/install.go | 2 +- pkg/action/show_test.go | 2 +- pkg/action/uninstall.go | 2 +- pkg/action/upgrade.go | 6 +- pkg/chart/v2/lint/rules/chartfile.go | 2 +- pkg/chart/v2/util/dependencies.go | 2 +- pkg/cli/values/options.go | 2 +- pkg/cmd/get_notes.go | 2 +- pkg/cmd/load_plugins.go | 4 +- pkg/cmd/package_test.go | 4 +- pkg/cmd/plugin_test.go | 2 +- pkg/cmd/repo_add.go | 2 +- pkg/cmd/root.go | 2 +- pkg/cmd/search_repo.go | 6 +- pkg/cmd/status.go | 2 +- pkg/downloader/chart_downloader.go | 2 +- pkg/downloader/chart_downloader_test.go | 2 +- pkg/downloader/manager.go | 88 ++++++++++--------- pkg/engine/engine.go | 2 +- pkg/kube/client.go | 2 +- pkg/kube/statuswait.go | 7 +- pkg/provenance/sign_test.go | 2 +- pkg/registry/chart.go | 8 +- pkg/registry/client.go | 3 +- pkg/registry/transport.go | 2 +- pkg/release/v1/util/manifest_sorter_test.go | 37 ++++---- pkg/repo/v1/index.go | 6 +- pkg/strvals/parser.go | 4 +- 36 files changed, 129 insertions(+), 132 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 518e47052..067b102d0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -91,18 +91,15 @@ linters: - commentFormatting - deferInLoop - elseif - - emptyStringTest - exposedSyncMutex - filepathJoin - hugeParam - ifElseChain - importShadow - - nestingReduce - nilValReturn - paramTypeCombine - ptrToRefParam - rangeValCopy - - singleCaseSwitch - todoCommentWithoutDetail - tooManyResultsChecker - uncheckedInlineErr diff --git a/internal/chart/v3/lint/rules/chartfile.go b/internal/chart/v3/lint/rules/chartfile.go index 29991a8d5..6f51a1de5 100644 --- a/internal/chart/v3/lint/rules/chartfile.go +++ b/internal/chart/v3/lint/rules/chartfile.go @@ -205,7 +205,7 @@ func validateChartDependencies(cf *chart.Metadata) error { } func validateChartType(cf *chart.Metadata) error { - if len(cf.Type) > 0 && cf.APIVersion != chart.APIVersionV3 { + if cf.Type != "" && cf.APIVersion != chart.APIVersionV3 { return fmt.Errorf("chart type is not valid in apiVersion '%s'. It is valid in apiVersion '%s'", cf.APIVersion, chart.APIVersionV3) } return nil diff --git a/internal/chart/v3/util/dependencies.go b/internal/chart/v3/util/dependencies.go index b31f7eb96..e1b3e0b97 100644 --- a/internal/chart/v3/util/dependencies.go +++ b/internal/chart/v3/util/dependencies.go @@ -43,7 +43,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values, } for _, r := range reqs { for c := range strings.SplitSeq(strings.TrimSpace(r.Condition), ",") { - if len(c) > 0 { + if c != "" { // retrieve value vv, err := cvals.PathValue(cpath + c) var errNoValue common.ErrNoValue diff --git a/internal/plugin/installer/http_installer_test.go b/internal/plugin/installer/http_installer_test.go index b080aa2ef..cbd868f3a 100644 --- a/internal/plugin/installer/http_installer_test.go +++ b/internal/plugin/installer/http_installer_test.go @@ -342,7 +342,7 @@ func TestMediaTypeToExtension(t *testing.T) { if shouldPass && ext == "" { t.Error("Expected an extension but got empty string") } - if !shouldPass && len(ext) != 0 { + if !shouldPass && ext != "" { t.Error("Expected extension to be empty for unrecognized type") } } diff --git a/internal/plugin/metadata.go b/internal/plugin/metadata.go index 562861b1c..53d6da4e6 100644 --- a/internal/plugin/metadata.go +++ b/internal/plugin/metadata.go @@ -165,7 +165,7 @@ func buildLegacyRuntimeConfig(m MetadataLegacy) RuntimeConfig { } platformCommand := m.PlatformCommand - if len(platformCommand) == 0 && len(m.Command) > 0 { + if len(platformCommand) == 0 && m.Command != "" { platformCommand = []PlatformCommand{{Command: m.Command}} } diff --git a/internal/plugin/metadata_legacy.go b/internal/plugin/metadata_legacy.go index 26132a783..0bc11a81a 100644 --- a/internal/plugin/metadata_legacy.go +++ b/internal/plugin/metadata_legacy.go @@ -81,7 +81,7 @@ func (m *MetadataLegacy) Validate() error { m.Usage = sanitizeString(m.Usage) - if len(m.PlatformCommand) > 0 && len(m.Command) > 0 { + if len(m.PlatformCommand) > 0 && m.Command != "" { return errors.New("both platformCommand and command are set") } diff --git a/internal/plugin/subprocess_commands.go b/internal/plugin/subprocess_commands.go index 211ce5ebf..eb3e2b11b 100644 --- a/internal/plugin/subprocess_commands.go +++ b/internal/plugin/subprocess_commands.go @@ -48,12 +48,12 @@ func getPlatformCommand(cmds []PlatformCommand) ([]string, []string) { return strings.Split(c.Command, " "), c.Args } - if (len(c.OperatingSystem) > 0 && !eq(c.OperatingSystem, runtime.GOOS)) || len(c.Architecture) > 0 { + if (c.OperatingSystem != "" && !eq(c.OperatingSystem, runtime.GOOS)) || c.Architecture != "" { // Skip if OS is not empty and doesn't match or if arch is set as a set arch requires an OS match continue } - if !foundOs && len(c.OperatingSystem) > 0 && eq(c.OperatingSystem, runtime.GOOS) { + if !foundOs && c.OperatingSystem != "" && eq(c.OperatingSystem, runtime.GOOS) { // First OS match with empty arch, can only be overridden by a direct match command = strings.Split(c.Command, " ") args = c.Args diff --git a/internal/release/v2/util/manifest_sorter_test.go b/internal/release/v2/util/manifest_sorter_test.go index 1c932d70c..eac9c9f36 100644 --- a/internal/release/v2/util/manifest_sorter_test.go +++ b/internal/release/v2/util/manifest_sorter_test.go @@ -149,27 +149,28 @@ metadata: for _, out := range hs { found := false for _, expect := range data { - if out.Path == expect.path { - found = true - assert.Equal(t, expect.path, out.Path) - nameFound := false - for _, expectedName := range expect.name { - if out.Name == expectedName { - nameFound = true - } + if out.Path != expect.path { + continue + } + found = true + assert.Equal(t, expect.path, out.Path) + nameFound := false + for _, expectedName := range expect.name { + if out.Name == expectedName { + nameFound = true } - assert.True(t, nameFound, "Got unexpected name %s", out.Name) - kindFound := false - for _, expectedKind := range expect.kind { - if out.Kind == expectedKind { - kindFound = true - } + } + assert.True(t, nameFound, "Got unexpected name %s", out.Name) + kindFound := false + for _, expectedKind := range expect.kind { + if out.Kind == expectedKind { + kindFound = true } - assert.True(t, kindFound, "Got unexpected kind %s", out.Kind) - - expectedHooks := expect.hooks[out.Name] - assert.Equal(t, expectedHooks, out.Events, "expected events: %v but got: %v", expectedHooks, out.Events) } + assert.True(t, kindFound, "Got unexpected kind %s", out.Kind) + + expectedHooks := expect.hooks[out.Name] + assert.Equal(t, expectedHooks, out.Events, "expected events: %v but got: %v", expectedHooks, out.Events) } assert.True(t, found, "Result not found: %v", out) } diff --git a/pkg/action/install.go b/pkg/action/install.go index d36d2fc3e..d73eca619 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -558,7 +558,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource } } - if len(i.Description) > 0 { + if i.Description != "" { rel.SetStatus(rcommon.StatusDeployed, i.Description) } else { rel.SetStatus(rcommon.StatusDeployed, "Install complete") diff --git a/pkg/action/show_test.go b/pkg/action/show_test.go index 854dee07a..0dde7fb74 100644 --- a/pkg/action/show_test.go +++ b/pkg/action/show_test.go @@ -86,7 +86,7 @@ func TestShowNoValues(t *testing.T) { t.Fatal(err) } - if len(output) != 0 { + if output != "" { t.Errorf("expected empty values buffer, got %s", output) } } diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index b14305112..47b2e47de 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -234,7 +234,7 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error) } rel.Info.Status = common.StatusUninstalled - if len(u.Description) > 0 { + if u.Description != "" { rel.Info.Description = u.Description } else { rel.Info.Description = "Uninstallation complete" diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 81d51164f..98ac44690 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -335,7 +335,7 @@ func (u *Upgrade) prepareUpgrade(ctx context.Context, name string, chart *chartv ApplyMethod: string(determineReleaseSSApplyMethod(serverSideApply)), } - if len(notesTxt) > 0 { + if notesTxt != "" { upgradedRelease.Info.Notes = notesTxt } err = validateManifest(u.cfg.KubeClient, manifestDoc.Bytes(), !u.DisableOpenAPIValidation) @@ -398,7 +398,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR if isDryRun(u.DryRunStrategy) { u.cfg.Logger().Debug("dry run for release", "name", upgradedRelease.Name) - if len(u.Description) > 0 { + if u.Description != "" { upgradedRelease.Info.Description = u.Description } else { upgradedRelease.Info.Description = "Dry run complete" @@ -516,7 +516,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele u.cfg.recordRelease(originalRelease) upgradedRelease.Info.Status = rcommon.StatusDeployed - if len(u.Description) > 0 { + if u.Description != "" { upgradedRelease.Info.Description = u.Description } else { upgradedRelease.Info.Description = "Upgrade complete" diff --git a/pkg/chart/v2/lint/rules/chartfile.go b/pkg/chart/v2/lint/rules/chartfile.go index f8f609280..cb58bbac0 100644 --- a/pkg/chart/v2/lint/rules/chartfile.go +++ b/pkg/chart/v2/lint/rules/chartfile.go @@ -216,7 +216,7 @@ func validateChartDependencies(cf *chart.Metadata) error { } func validateChartType(cf *chart.Metadata) error { - if len(cf.Type) > 0 && cf.APIVersion != chart.APIVersionV2 { + if cf.Type != "" && cf.APIVersion != chart.APIVersionV2 { return fmt.Errorf("chart type is not valid in apiVersion '%s'. It is valid in apiVersion '%s'", cf.APIVersion, chart.APIVersionV2) } return nil diff --git a/pkg/chart/v2/util/dependencies.go b/pkg/chart/v2/util/dependencies.go index f28a4f4b1..019cd1cda 100644 --- a/pkg/chart/v2/util/dependencies.go +++ b/pkg/chart/v2/util/dependencies.go @@ -43,7 +43,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values, } for _, r := range reqs { for c := range strings.SplitSeq(strings.TrimSpace(r.Condition), ",") { - if len(c) > 0 { + if c != "" { // retrieve value vv, err := cvals.PathValue(cpath + c) var errNoValue common.ErrNoValue diff --git a/pkg/cli/values/options.go b/pkg/cli/values/options.go index 336dfb72b..6edfb415c 100644 --- a/pkg/cli/values/options.go +++ b/pkg/cli/values/options.go @@ -62,7 +62,7 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]any, error) { // User specified a value via --set-json for _, value := range opts.JSONValues { trimmedValue := strings.TrimSpace(value) - if len(trimmedValue) > 0 && trimmedValue[0] == '{' { + if trimmedValue != "" && trimmedValue[0] == '{' { // If value is JSON object format, parse it as map var jsonMap map[string]any if err := json.Unmarshal([]byte(trimmedValue), &jsonMap); err != nil { diff --git a/pkg/cmd/get_notes.go b/pkg/cmd/get_notes.go index 46fbeeaf5..7f8754f3c 100644 --- a/pkg/cmd/get_notes.go +++ b/pkg/cmd/get_notes.go @@ -55,7 +55,7 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if err != nil { return err } - if len(rac.Notes()) > 0 { + if rac.Notes() != "" { fmt.Fprintf(out, "NOTES:\n%s\n", rac.Notes()) } return nil diff --git a/pkg/cmd/load_plugins.go b/pkg/cmd/load_plugins.go index 474eaea2e..dee42f750 100644 --- a/pkg/cmd/load_plugins.go +++ b/pkg/cmd/load_plugins.go @@ -242,7 +242,7 @@ func addPluginCommands(plug plugin.Plugin, baseCmd *cobra.Command, cmds *pluginC return } - if len(cmds.Name) == 0 { + if cmds.Name == "" { slog.Debug("sub-command name field missing", slog.String("commandPath", baseCmd.CommandPath())) return } @@ -380,7 +380,7 @@ func pluginDynamicComp(plug plugin.Plugin, cmd *cobra.Command, args []string, to var completions []string for comp := range strings.SplitSeq(buf.String(), "\n") { // Remove any empty lines - if len(comp) > 0 { + if comp != "" { completions = append(completions, comp) } } diff --git a/pkg/cmd/package_test.go b/pkg/cmd/package_test.go index db4a2523a..5705c9b9e 100644 --- a/pkg/cmd/package_test.go +++ b/pkg/cmd/package_test.go @@ -119,7 +119,7 @@ func TestPackage(t *testing.T) { } // This is an unfortunate byproduct of the tmpdir - if v, ok := tt.flags["keyring"]; ok && len(v) > 0 { + if v, ok := tt.flags["keyring"]; ok && v != "" { tt.flags["keyring"] = filepath.Join(origDir, v) } @@ -147,7 +147,7 @@ func TestPackage(t *testing.T) { t.Fatalf("%q: expected error %q, got %q", tt.name, tt.expect, err) } - if len(tt.hasfile) > 0 { + if tt.hasfile != "" { if fi, err := os.Stat(tt.hasfile); err != nil { t.Errorf("%q: expected file %q, got err %q", tt.name, tt.hasfile, err) } else if fi.Size() == 0 { diff --git a/pkg/cmd/plugin_test.go b/pkg/cmd/plugin_test.go index 0a6435d99..476997110 100644 --- a/pkg/cmd/plugin_test.go +++ b/pkg/cmd/plugin_test.go @@ -291,7 +291,7 @@ func checkCommand(t *testing.T, plugins []*cobra.Command, tests []staticCompleti var pflags []string pp.LocalFlags().VisitAll(func(flag *pflag.Flag) { pflags = append(pflags, flag.Name) - if len(flag.Shorthand) > 0 && flag.Shorthand != flag.Name { + if flag.Shorthand != "" && flag.Shorthand != flag.Name { pflags = append(pflags, flag.Shorthand) } }) diff --git a/pkg/cmd/repo_add.go b/pkg/cmd/repo_add.go index 3fc1a7249..a7a02ef75 100644 --- a/pkg/cmd/repo_add.go +++ b/pkg/cmd/repo_add.go @@ -121,7 +121,7 @@ func (o *repoAddOptions) run(out io.Writer) error { // Acquire a file lock for process synchronization repoFileExt := filepath.Ext(o.repoFile) var lockPath string - if len(repoFileExt) > 0 && len(repoFileExt) < len(o.repoFile) { + if repoFileExt != "" && len(repoFileExt) < len(o.repoFile) { lockPath = strings.TrimSuffix(o.repoFile, repoFileExt) + ".lock" } else { lockPath = o.repoFile + ".lock" diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 3203ced25..7ee71f4ef 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -238,7 +238,7 @@ func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, arg cobra.CompDebugln("About to get the different kube-contexts", settings.Debug) loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() - if len(settings.KubeConfig) > 0 { + if settings.KubeConfig != "" { loadingRules = &clientcmd.ClientConfigLoadingRules{ExplicitPath: settings.KubeConfig} } if config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( diff --git a/pkg/cmd/search_repo.go b/pkg/cmd/search_repo.go index 53626f1b6..9c5b1dd18 100644 --- a/pkg/cmd/search_repo.go +++ b/pkg/cmd/search_repo.go @@ -194,7 +194,7 @@ func (o *searchRepoOptions) buildIndex() (*search.Index, error) { continue } - i.AddRepo(n, ind, o.versions || len(o.version) > 0) + i.AddRepo(n, ind, o.versions || o.version != "") } return i, nil } @@ -361,7 +361,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell // 2- If there is some input from the user (or else we will end up // listing the entire content of the current directory which will // be too many choices for the user to find the real repos) - if includeFiles && len(completions) > 0 && len(toComplete) > 0 { + if includeFiles && len(completions) > 0 && toComplete != "" { if files, err := os.ReadDir("."); err == nil { for _, file := range files { if strings.HasPrefix(file.Name(), toComplete) { @@ -375,7 +375,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell // If the user didn't provide any input to completion, // we provide a hint that a path can also be used - if includeFiles && len(toComplete) == 0 { + if includeFiles && toComplete == "" { completions = append(completions, "./\tRelative path prefix to local chart", "/\tAbsolute path prefix to local chart") } cobra.CompDebugln(fmt.Sprintf("Completions after checking empty input: %v", completions), settings.Debug) diff --git a/pkg/cmd/status.go b/pkg/cmd/status.go index 705691aaf..6998e341a 100644 --- a/pkg/cmd/status.go +++ b/pkg/cmd/status.go @@ -236,7 +236,7 @@ func (s statusPrinter) WriteTable(out io.Writer) error { } // Hide notes from output - option in install and upgrades - if !s.hideNotes && len(rel.Info.Notes) > 0 { + if !s.hideNotes && rel.Info.Notes != "" { _, _ = fmt.Fprintf(out, "NOTES:\n%s\n", strings.TrimSpace(rel.Info.Notes)) } return nil diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 4a13fc5ed..eca4e3342 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -382,7 +382,7 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (string, *url return "", u, err } - if u.IsAbs() && len(u.Host) > 0 && len(u.Path) > 0 { + if u.IsAbs() && u.Host != "" && u.Path != "" { // In this case, we have to find the parent repo that contains this chart // URL. And this is an unfortunate problem, as it requires actually going // through each repo cache file and finding a matching URL. But basically diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index 07b25a1eb..b3899d813 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -167,7 +167,7 @@ func TestVerifyChart(t *testing.T) { } // The verification is tested at length in the provenance package. Here, // we just want a quick sanity check that the v is not empty. - if len(v.FileHash) == 0 { + if v.FileHash == "" { t.Error("Digest missing") } } diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 543f685eb..683f96007 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -441,21 +441,22 @@ func (m *Manager) safeMoveDeps(deps []*chart.Dependency, source, dest string) er fmt.Fprintln(m.Out, "Deleting outdated charts") // find all files that exist in dest that do not exist in source; delete them (outdated dependencies) for _, file := range destFiles { - if !file.IsDir() && !existsInSourceDirectory[file.Name()] { - fname := filepath.Join(dest, file.Name()) - ch, err := loader.LoadFile(fname) - if err != nil { - fmt.Fprintf(m.Out, "Could not verify %s for deletion: %s (Skipping)\n", fname, err) - continue - } - // local dependency - skip - if isLocalDependency[ch.Name()] { - continue - } - if err := os.Remove(fname); err != nil { - fmt.Fprintf(m.Out, "Could not delete %s: %s (Skipping)", fname, err) - continue - } + if file.IsDir() || existsInSourceDirectory[file.Name()] { + continue + } + fname := filepath.Join(dest, file.Name()) + ch, err := loader.LoadFile(fname) + if err != nil { + fmt.Fprintf(m.Out, "Could not verify %s for deletion: %s (Skipping)\n", fname, err) + continue + } + // local dependency - skip + if isLocalDependency[ch.Name()] { + continue + } + if err := os.Remove(fname); err != nil { + fmt.Fprintf(m.Out, "Could not delete %s: %s (Skipping)", fname, err) + continue } } @@ -728,37 +729,38 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]* } for _, cr := range repos { - if urlutil.Equal(repoURL, cr.Config.URL) { - var entry repo.ChartVersions - entry, err = findEntryByName(name, cr) - if err != nil { - // TODO: Where linting is skipped in this function we should - // refactor to remove naked returns while ensuring the same - // behavior - //nolint:nakedret - return - } - var ve *repo.ChartVersion - ve, err = findVersionedEntry(version, entry) - if err != nil { - //nolint:nakedret - return - } - url, err = repo.ResolveReferenceURL(repoURL, ve.URLs[0]) - if err != nil { - //nolint:nakedret - return - } - username = cr.Config.Username - password = cr.Config.Password - passCredentialsAll = cr.Config.PassCredentialsAll - insecureSkipTLSVerify = cr.Config.InsecureSkipTLSVerify - caFile = cr.Config.CAFile - certFile = cr.Config.CertFile - keyFile = cr.Config.KeyFile + if !urlutil.Equal(repoURL, cr.Config.URL) { + continue + } + var entry repo.ChartVersions + entry, err = findEntryByName(name, cr) + if err != nil { + // TODO: Where linting is skipped in this function we should + // refactor to remove naked returns while ensuring the same + // behavior + //nolint:nakedret + return + } + var ve *repo.ChartVersion + ve, err = findVersionedEntry(version, entry) + if err != nil { + //nolint:nakedret + return + } + url, err = repo.ResolveReferenceURL(repoURL, ve.URLs[0]) + if err != nil { //nolint:nakedret return } + username = cr.Config.Username + password = cr.Config.Password + passCredentialsAll = cr.Config.PassCredentialsAll + insecureSkipTLSVerify = cr.Config.InsecureSkipTLSVerify + caFile = cr.Config.CAFile + certFile = cr.Config.CertFile + keyFile = cr.Config.KeyFile + //nolint:nakedret + return } url, err = repo.FindChartInRepoURL(repoURL, name, m.Getters, repo.WithChartVersion(version), repo.WithClientTLS(certFile, keyFile, caFile)) if err == nil { diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index d304aea7d..1d0affd93 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -437,7 +437,7 @@ func parseTemplateSimpleErrorString(remainder string) (TraceableError, bool) { // Matches https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=141 func parseTemplateExecutingAtErrorType(remainder string) (TraceableError, bool) { if templateName, after, found := strings.Cut(remainder, ": executing "); found { - if len(after) == 0 || after[0] != '"' { + if after == "" || after[0] != '"' { return TraceableError{}, false } // find closing quote for function name diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 7b02f47ef..4e6d5c3a9 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -961,7 +961,7 @@ func getManagedFieldsManager() string { } // When no manager is set and no calling application can be found it is unknown - if len(os.Args[0]) == 0 { + if os.Args[0] == "" { return "unknown" } diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index 29de0af2b..12c3e95a6 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -180,11 +180,8 @@ func (w *statusWaiter) wait(ctx context.Context, resourceList ResourceList, sw w defer cancel() resources := []object.ObjMetadata{} for _, resource := range resourceList { - switch value := AsVersioned(resource).(type) { - case *appsv1.Deployment: - if value.Spec.Paused { - continue - } + if value, ok := AsVersioned(resource).(*appsv1.Deployment); ok && value.Spec.Paused { + continue } obj, err := object.RuntimeToObjMeta(resource.Object) if err != nil { diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go index 8784e6c12..faf402b43 100644 --- a/pkg/provenance/sign_test.go +++ b/pkg/provenance/sign_test.go @@ -380,7 +380,7 @@ func TestVerify(t *testing.T) { if ver, err := signer.Verify(archiveData, sigData, filepath.Base(testChartfile)); err != nil { t.Errorf("Failed to pass verify. Err: %s", err) - } else if len(ver.FileHash) == 0 { + } else if ver.FileHash == "" { t.Error("Verification is missing hash.") } else if ver.SignedBy == nil { t.Error("No SignedBy field") diff --git a/pkg/registry/chart.go b/pkg/registry/chart.go index d70b7e912..7c41785c5 100644 --- a/pkg/registry/chart.go +++ b/pkg/registry/chart.go @@ -72,7 +72,7 @@ func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[ chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationVersion, meta.Version) chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationURL, meta.Home) - if len(creationTime) == 0 { + if creationTime == "" { creationTime = time.Now().UTC().Format(time.RFC3339) } @@ -86,11 +86,11 @@ func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[ var maintainerSb strings.Builder for maintainerIdx, maintainer := range meta.Maintainers { - if len(maintainer.Name) > 0 { + if maintainer.Name != "" { maintainerSb.WriteString(maintainer.Name) } - if len(maintainer.Email) > 0 { + if maintainer.Email != "" { maintainerSb.WriteString(" (") maintainerSb.WriteString(maintainer.Email) maintainerSb.WriteString(")") @@ -110,7 +110,7 @@ func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[ // addToMap takes an existing map and adds an item if the value is not empty func addToMap(inputMap map[string]string, newKey string, newValue string) map[string]string { // Add item to map if its - if len(strings.TrimSpace(newValue)) > 0 { + if strings.TrimSpace(newValue) != "" { inputMap[newKey] = newValue } diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 4d7da78dc..a14afdd1f 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -303,8 +303,7 @@ func ensureTLSConfig(client *auth.Client, setConfig *tls.Config) (*tls.Config, e case *http.Transport: transport = t case *LoggingTransport: - switch t := t.RoundTripper.(type) { - case *http.Transport: + if t, ok := t.RoundTripper.(*http.Transport); ok { transport = t } } diff --git a/pkg/registry/transport.go b/pkg/registry/transport.go index e4177efb3..68e4ac587 100644 --- a/pkg/registry/transport.go +++ b/pkg/registry/transport.go @@ -130,7 +130,7 @@ func logResponseBody(resp *http.Response) string { } readBody := buf.String() - if len(readBody) == 0 { + if readBody == "" { return " Response body is empty" } if containsCredentials(readBody) { diff --git a/pkg/release/v1/util/manifest_sorter_test.go b/pkg/release/v1/util/manifest_sorter_test.go index b02400938..35c7a0721 100644 --- a/pkg/release/v1/util/manifest_sorter_test.go +++ b/pkg/release/v1/util/manifest_sorter_test.go @@ -149,27 +149,28 @@ metadata: for _, out := range hs { found := false for _, expect := range data { - if out.Path == expect.path { - found = true - assert.Equal(t, expect.path, out.Path) - nameFound := false - for _, expectedName := range expect.name { - if out.Name == expectedName { - nameFound = true - } + if out.Path != expect.path { + continue + } + found = true + assert.Equal(t, expect.path, out.Path) + nameFound := false + for _, expectedName := range expect.name { + if out.Name == expectedName { + nameFound = true } - assert.True(t, nameFound, "Got unexpected name %s", out.Name) - kindFound := false - for _, expectedKind := range expect.kind { - if out.Kind == expectedKind { - kindFound = true - } + } + assert.True(t, nameFound, "Got unexpected name %s", out.Name) + kindFound := false + for _, expectedKind := range expect.kind { + if out.Kind == expectedKind { + kindFound = true } - assert.True(t, kindFound, "Got unexpected kind %s", out.Kind) - - expectedHooks := expect.hooks[out.Name] - assert.Equal(t, expectedHooks, out.Events, "expected events: %v but got: %v", expectedHooks, out.Events) } + assert.True(t, kindFound, "Got unexpected kind %s", out.Kind) + + expectedHooks := expect.hooks[out.Name] + assert.Equal(t, expectedHooks, out.Events, "expected events: %v but got: %v", expectedHooks, out.Events) } assert.True(t, found, "Result not found: %v", out) } diff --git a/pkg/repo/v1/index.go b/pkg/repo/v1/index.go index 57e4c081f..15f72891b 100644 --- a/pkg/repo/v1/index.go +++ b/pkg/repo/v1/index.go @@ -214,7 +214,7 @@ func (i IndexFile) Get(name, version string) (*ChartVersion, error) { } // when customer inputs specific version, check whether there's an exact match first - if len(version) != 0 { + if version != "" { for _, ver := range vs { if version == ver.Version { return ver, nil @@ -229,9 +229,9 @@ func (i IndexFile) Get(name, version string) (*ChartVersion, error) { } if constraint.Check(test) { - if len(version) != 0 && !isVersionRange(version) { + if version != "" && !isVersionRange(version) { slog.Warn("unable to find exact version requested; falling back to closest available version", "chart", name, "requested", version, "selected", ver.Version) - } else if len(version) != 0 && isVersionRange(version) { + } else if version != "" && isVersionRange(version) { slog.Debug("selected version matching constraint", "chart", name, "constraint", version, "selected", ver.Version) } return ver, nil diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index a0b72bee3..c52e8aed9 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -290,7 +290,7 @@ func (t *parser) key(data map[string]any, nestedNameLevel int) (reterr error) { func set(data map[string]any, key string, val any) { // If key is empty, don't set it. - if len(key) == 0 { + if key == "" { return } data[key] = val @@ -549,7 +549,7 @@ func typedVal(v []rune, st bool) any { } // If this value does not start with zero, try parsing it to an int - if len(val) != 0 && val[0] != '0' { + if val != "" && val[0] != '0' { if iv, err := strconv.ParseInt(val, 10, 64); err == nil { return iv } From e3fbe2e9d10985fa30aeeb678c376d9556a2b6f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 21:33:29 +0000 Subject: [PATCH 408/571] chore(deps): bump golang.org/x/text from 0.38.0 to 0.39.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.38.0 to 0.39.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.38.0...v0.39.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.39.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 628b3f374..b677fce6e 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.53.0 golang.org/x/term v0.44.0 - golang.org/x/text v0.38.0 + golang.org/x/text v0.39.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.36.2 k8s.io/apiextensions-apiserver v0.36.2 @@ -158,13 +158,13 @@ require ( go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/mod v0.36.0 // indirect + golang.org/x/mod v0.37.0 // indirect golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.21.0 // indirect golang.org/x/sys v0.46.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.45.0 // indirect + golang.org/x/tools v0.47.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/grpc v1.80.0 // indirect diff --git a/go.sum b/go.sum index dc469fde9..27bf11eac 100644 --- a/go.sum +++ b/go.sum @@ -390,8 +390,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -450,8 +450,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus= +golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -460,8 +460,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= From ca24a55c314d2fd84430d88c55ce09a391798d8e Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Wed, 8 Jul 2026 11:32:56 +0200 Subject: [PATCH 409/571] fix(ci): resolve Node 20 deprecation in govulncheck The pinned v1.0.4 used actions/checkout@v4 and actions/setup-go@v5 internally, both built on Node 20. Latest main uses v6 of both, which run on Node 24. Signed-off-by: Benoit Tigeot --- .github/workflows/govulncheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 93ab559fc..4dc8a84f5 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -30,6 +30,6 @@ jobs: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true - name: govulncheck - uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # pin@1.0.4 + uses: golang/govulncheck-action@032d45514ae346b1db93c04b0c90b841c370344f # pin@main 2026-07-06 with: go-package: ./... From ed651de9b12ff98c0aa13985a88bca9aca0aa4fb Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Wed, 8 Jul 2026 11:58:43 +0200 Subject: [PATCH 410/571] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Benoit Tigeot --- .github/workflows/govulncheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 4dc8a84f5..5e5111432 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -30,6 +30,6 @@ jobs: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true - name: govulncheck - uses: golang/govulncheck-action@032d45514ae346b1db93c04b0c90b841c370344f # pin@main 2026-07-06 + uses: golang/govulncheck-action@032d45514ae346b1db93c04b0c90b841c370344f # pin@032d455 (from main 2026-07-06) with: go-package: ./... From f25f9555785b15fc7f41aa2e5e43979b1bca5021 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 18:00:43 -0600 Subject: [PATCH 411/571] chore(deps): bump github/codeql-action/upload-sarif (#32311) Bumps [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) from 4.36.3 to 4.37.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/54f647b7e1bb85c95cddabcd46b0c578ec92bc1a...99df26d4f13ea111d4ec1a7dddef6063f76b97e9) --- updated-dependencies: - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.37.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index e90e91bb3..343669859 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 + uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 with: sarif_file: results.sarif From a242855a7e2c31efd1f0f69ad1c5ae24be0563de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 18:00:46 -0600 Subject: [PATCH 412/571] chore(deps): bump golang.org/x/text from 0.39.0 to 0.40.0 (#32309) Bumps [golang.org/x/text](https://github.com/golang/text) from 0.39.0 to 0.40.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.39.0...v0.40.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.40.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index b677fce6e..2e3b11297 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.53.0 golang.org/x/term v0.44.0 - golang.org/x/text v0.39.0 + golang.org/x/text v0.40.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.36.2 k8s.io/apiextensions-apiserver v0.36.2 @@ -161,7 +161,7 @@ require ( golang.org/x/mod v0.37.0 // indirect golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.21.0 // indirect + golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.46.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.47.0 // indirect diff --git a/go.sum b/go.sum index 27bf11eac..3ca6d6f5e 100644 --- a/go.sum +++ b/go.sum @@ -414,8 +414,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -450,8 +450,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus= -golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From ea52bdf7e2c5155d629c85aa7eb7f625eef92936 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 18:01:56 -0600 Subject: [PATCH 413/571] chore(deps): bump github/codeql-action/autobuild from 4.36.2 to 4.37.0 (#32305) Bumps [github/codeql-action/autobuild](https://github.com/github/codeql-action) from 4.36.2 to 4.37.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...99df26d4f13ea111d4ec1a7dddef6063f76b97e9) --- updated-dependencies: - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 323b8ec5b..29ce03ddf 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # pinv4.36.2 + uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # pinv4.37.0 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl From a71eb99db4f0c08fae21d3bfafa3bdfb6465b2a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 18:03:56 -0600 Subject: [PATCH 414/571] chore(deps): bump github/codeql-action/init from 4.36.2 to 4.37.0 (#32313) Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.36.2 to 4.37.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...99df26d4f13ea111d4ec1a7dddef6063f76b97e9) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.37.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 29ce03ddf..13303ab16 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # pinv4.36.2 + uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # pinv4.37.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. From 71901a4c7cc6280317b892341b5dde5a866c952c Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Thu, 9 Jul 2026 05:49:00 -0600 Subject: [PATCH 415/571] chore(deps): bump github/codeql-action/analyze from 4.36.2 to 4.37.0 The init and autobuild steps were bumped to 4.37.0 (#32313, #32305) but the analyze step remained at 4.36.2, causing CodeQL to fail on main with: "Loaded a configuration file for version '4.37.0', but running version '4.36.2'". Align analyze with the other codeql-action steps. Signed-off-by: Terry Howe --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 13303ab16..e87ec8ec2 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # pinv4.36.2 + uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # pinv4.37.0 From 0fc3b9385348b0d346728de3b0969ae2a3b7d47f Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Thu, 9 Jul 2026 05:41:28 -0600 Subject: [PATCH 416/571] chore(deps): bump golang.org/x/crypto from 0.53.0 to 0.54.0 x/crypto v0.54.0 raises golang.org/x/term to v0.45.0 and golang.org/x/sys to v0.47.0 via minimum version selection. Signed-off-by: Terry Howe --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 2e3b11297..b3c6eb290 100644 --- a/go.mod +++ b/go.mod @@ -35,8 +35,8 @@ require ( github.com/stretchr/testify v1.11.1 github.com/tetratelabs/wazero v1.12.0 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.53.0 - golang.org/x/term v0.44.0 + golang.org/x/crypto v0.54.0 + golang.org/x/term v0.45.0 golang.org/x/text v0.40.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.36.2 @@ -162,7 +162,7 @@ require ( golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.22.0 // indirect - golang.org/x/sys v0.46.0 // indirect + golang.org/x/sys v0.47.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.47.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect diff --git a/go.sum b/go.sum index 3ca6d6f5e..06539321b 100644 --- a/go.sum +++ b/go.sum @@ -384,8 +384,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -432,8 +432,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -441,8 +441,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= From 8118a4da1ed0ba470571ee3e150e4949b373ba9c Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Thu, 9 Jul 2026 05:49:00 -0600 Subject: [PATCH 417/571] chore(deps): bump github/codeql-action/analyze from 4.36.2 to 4.37.0 The init and autobuild steps were bumped to 4.37.0 (#32313, #32305) but the analyze step remained at 4.36.2, causing CodeQL to fail on main with: "Loaded a configuration file for version '4.37.0', but running version '4.36.2'". Align analyze with the other codeql-action steps. Signed-off-by: Terry Howe --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 13303ab16..e87ec8ec2 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # pinv4.36.2 + uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # pinv4.37.0 From 7bfcdb0fc34894ec6f1622f48608161188b2e2a8 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 10 Jul 2026 10:04:48 +0200 Subject: [PATCH 418/571] fix(linters): update golangci-lint to 2.12.2 Signed-off-by: Matthieu MOREL --- .github/env | 2 +- .golangci.yml | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/env b/.github/env index 43ebdd36e..9a0c1ef4c 100644 --- a/.github/env +++ b/.github/env @@ -1,2 +1,2 @@ GOLANG_VERSION=1.26 -GOLANGCI_LINT_VERSION=v2.11.3 +GOLANGCI_LINT_VERSION=v2.12.2 diff --git a/.golangci.yml b/.golangci.yml index 1cd720d47..606613b4e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -27,7 +27,7 @@ linters: - exhaustive - fatcontext - gocritic - - gomodguard + - gomodguard_v2 - govet - ineffassign - misspell @@ -107,12 +107,11 @@ linters: - whyNoLint enable-all: true - gomodguard: + gomodguard_v2: blocked: - modules: - - github.com/evanphx/json-patch: - recommendations: - - github.com/evanphx/json-patch/v5 + - module: github.com/evanphx/json-patch + recommendations: + - github.com/evanphx/json-patch/v5 nolintlint: require-specific: true From 9662fdd73b848e04889b452335b60250414ef6db Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 10 Jul 2026 11:03:22 +0200 Subject: [PATCH 419/571] refactor: enable several checks from gocritic Signed-off-by: Matthieu MOREL --- .golangci.yml | 5 ----- internal/chart/v3/lint/rules/values_test.go | 2 +- internal/chart/v3/util/create_test.go | 2 +- internal/release/v2/util/manifest.go | 2 +- internal/third_party/dep/fs/fs_test.go | 6 +++--- pkg/action/dependency.go | 2 +- pkg/action/install_test.go | 24 ++++++++++----------- pkg/action/list.go | 2 +- pkg/chart/v2/lint/rules/values_test.go | 2 +- pkg/chart/v2/util/create_test.go | 2 +- pkg/cmd/dependency_build_test.go | 2 +- pkg/cmd/repo_add_test.go | 2 +- pkg/cmd/repo_remove_test.go | 2 +- pkg/cmd/search_repo.go | 6 +++--- pkg/cmd/show_test.go | 1 - pkg/cmd/template_test.go | 2 +- pkg/downloader/cache_test.go | 4 ++-- pkg/downloader/chart_downloader_test.go | 2 +- pkg/downloader/manager.go | 2 +- pkg/engine/funcs_test.go | 2 +- pkg/getter/httpgetter_test.go | 4 ++-- pkg/helmpath/lazypath_unix_test.go | 6 +++--- pkg/kube/ready_test.go | 2 +- pkg/registry/client_scope_test.go | 4 ++-- pkg/registry/registry_test.go | 2 +- pkg/release/v1/util/manifest.go | 2 +- pkg/repo/v1/index.go | 2 +- pkg/storage/driver/sql_test.go | 14 ++++++------ pkg/storage/storage_test.go | 1 - pkg/strvals/literal_parser_test.go | 2 +- pkg/strvals/parser.go | 2 -- pkg/strvals/parser_test.go | 2 +- 32 files changed, 55 insertions(+), 62 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 1cd720d47..4cde6b1e9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -84,19 +84,14 @@ linters: gocritic: disabled-checks: - appendAssign - - appendCombine - - assignOp - badCall - commentedOutCode - - commentFormatting - deferInLoop - elseif - exposedSyncMutex - - filepathJoin - hugeParam - ifElseChain - importShadow - - nilValReturn - paramTypeCombine - ptrToRefParam - rangeValCopy diff --git a/internal/chart/v3/lint/rules/values_test.go b/internal/chart/v3/lint/rules/values_test.go index 36f5c403e..470904049 100644 --- a/internal/chart/v3/lint/rules/values_test.go +++ b/internal/chart/v3/lint/rules/values_test.go @@ -26,7 +26,7 @@ import ( "helm.sh/helm/v4/internal/test/ensure" ) -var nonExistingValuesFilePath = filepath.Join("/fake/dir", "values.yaml") +var nonExistingValuesFilePath = filepath.FromSlash("/fake/dir/values.yaml") const testSchema = ` { diff --git a/internal/chart/v3/util/create_test.go b/internal/chart/v3/util/create_test.go index abdd52a82..834ab3662 100644 --- a/internal/chart/v3/util/create_test.go +++ b/internal/chart/v3/util/create_test.go @@ -121,7 +121,7 @@ func TestCreate_Overwrite(t *testing.T) { dir := filepath.Join(tdir, "foo") - tplname := filepath.Join(dir, "templates/hpa.yaml") + tplname := filepath.Join(dir, "templates", "hpa.yaml") writeFile(tplname, []byte("FOO")) // Now re-run the create diff --git a/internal/release/v2/util/manifest.go b/internal/release/v2/util/manifest.go index 5dbcdaea5..79f651c54 100644 --- a/internal/release/v2/util/manifest.go +++ b/internal/release/v2/util/manifest.go @@ -64,7 +64,7 @@ func SplitManifests(bigFile string) map[string]string { d = strings.TrimLeftFunc(d, unicode.IsSpace) res[fmt.Sprintf(tpl, count)] = d - count = count + 1 + count++ } return res } diff --git a/internal/third_party/dep/fs/fs_test.go b/internal/third_party/dep/fs/fs_test.go index 6eed3f055..797890f9f 100644 --- a/internal/third_party/dep/fs/fs_test.go +++ b/internal/third_party/dep/fs/fs_test.go @@ -358,9 +358,9 @@ func TestCopyFileSymlink(t *testing.T) { tempdir := t.TempDir() testcases := map[string]string{ - filepath.Join("./testdata/symlinks/file-symlink"): filepath.Join(tempdir, "dst-file"), - filepath.Join("./testdata/symlinks/windows-file-symlink"): filepath.Join(tempdir, "windows-dst-file"), - filepath.Join("./testdata/symlinks/invalid-symlink"): filepath.Join(tempdir, "invalid-symlink"), + filepath.Join(".", "testdata", "symlinks", "file-symlink"): filepath.Join(tempdir, "dst-file"), + filepath.Join(".", "testdata", "symlinks", "windows-file-symlink"): filepath.Join(tempdir, "windows-dst-file"), + filepath.Join(".", "testdata", "symlinks", "invalid-symlink"): filepath.Join(tempdir, "invalid-symlink"), } for symlink, dst := range testcases { diff --git a/pkg/action/dependency.go b/pkg/action/dependency.go index 8ceca1433..895c11e63 100644 --- a/pkg/action/dependency.go +++ b/pkg/action/dependency.go @@ -201,7 +201,7 @@ func (d *Dependency) printDependencies(chartpath string, out io.Writer, c *chart // printMissing prints warnings about charts that are present on disk, but are // not in Chart.yaml. func (d *Dependency) printMissing(chartpath string, out io.Writer, reqs []*chart.Dependency) { - folder := filepath.Join(chartpath, "charts/*") + folder := filepath.Join(chartpath, "charts", "*") files, err := filepath.Glob(folder) if err != nil { fmt.Fprintln(out, err) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 0d0e58e4a..f79590670 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -868,21 +868,21 @@ func TestInstallReleaseOutputDir(t *testing.T) { t.Fatalf("Failed install: %s", err) } - _, err = os.Stat(filepath.Join(dir, "hello/templates/goodbye")) + _, err = os.Stat(filepath.Join(dir, "hello", "templates", "goodbye")) req.NoError(err) - _, err = os.Stat(filepath.Join(dir, "hello/templates/hello")) + _, err = os.Stat(filepath.Join(dir, "hello", "templates", "hello")) req.NoError(err) - _, err = os.Stat(filepath.Join(dir, "hello/templates/with-partials")) + _, err = os.Stat(filepath.Join(dir, "hello", "templates", "with-partials")) req.NoError(err) - _, err = os.Stat(filepath.Join(dir, "hello/templates/rbac")) + _, err = os.Stat(filepath.Join(dir, "hello", "templates", "rbac")) req.NoError(err) - test.AssertGoldenFile(t, filepath.Join(dir, "hello/templates/rbac"), "rbac.txt") + test.AssertGoldenFile(t, filepath.Join(dir, "hello", "templates", "rbac"), "rbac.txt") - _, err = os.Stat(filepath.Join(dir, "hello/templates/empty")) + _, err = os.Stat(filepath.Join(dir, "hello", "templates", "empty")) is.ErrorIs(err, fs.ErrNotExist) } @@ -905,21 +905,21 @@ func TestInstallOutputDirWithReleaseName(t *testing.T) { t.Fatalf("Failed install: %s", err) } - _, err = os.Stat(filepath.Join(newDir, "hello/templates/goodbye")) + _, err = os.Stat(filepath.Join(newDir, "hello", "templates", "goodbye")) req.NoError(err) - _, err = os.Stat(filepath.Join(newDir, "hello/templates/hello")) + _, err = os.Stat(filepath.Join(newDir, "hello", "templates", "hello")) req.NoError(err) - _, err = os.Stat(filepath.Join(newDir, "hello/templates/with-partials")) + _, err = os.Stat(filepath.Join(newDir, "hello", "templates", "with-partials")) req.NoError(err) - _, err = os.Stat(filepath.Join(newDir, "hello/templates/rbac")) + _, err = os.Stat(filepath.Join(newDir, "hello", "templates", "rbac")) req.NoError(err) - test.AssertGoldenFile(t, filepath.Join(newDir, "hello/templates/rbac"), "rbac.txt") + test.AssertGoldenFile(t, filepath.Join(newDir, "hello", "templates", "rbac"), "rbac.txt") - _, err = os.Stat(filepath.Join(newDir, "hello/templates/empty")) + _, err = os.Stat(filepath.Join(newDir, "hello", "templates", "empty")) is.ErrorIs(err, fs.ErrNotExist) } diff --git a/pkg/action/list.go b/pkg/action/list.go index 06727bd9a..a055f760d 100644 --- a/pkg/action/list.go +++ b/pkg/action/list.go @@ -178,7 +178,7 @@ func (l *List) Run() ([]ri.Releaser, error) { } if results == nil { - return results, nil + return nil, nil } rresults, err := releaseListToV1List(results) diff --git a/pkg/chart/v2/lint/rules/values_test.go b/pkg/chart/v2/lint/rules/values_test.go index 36f5c403e..470904049 100644 --- a/pkg/chart/v2/lint/rules/values_test.go +++ b/pkg/chart/v2/lint/rules/values_test.go @@ -26,7 +26,7 @@ import ( "helm.sh/helm/v4/internal/test/ensure" ) -var nonExistingValuesFilePath = filepath.Join("/fake/dir", "values.yaml") +var nonExistingValuesFilePath = filepath.FromSlash("/fake/dir/values.yaml") const testSchema = ` { diff --git a/pkg/chart/v2/util/create_test.go b/pkg/chart/v2/util/create_test.go index 967972fc8..367b755d9 100644 --- a/pkg/chart/v2/util/create_test.go +++ b/pkg/chart/v2/util/create_test.go @@ -121,7 +121,7 @@ func TestCreate_Overwrite(t *testing.T) { dir := filepath.Join(tdir, "foo") - tplname := filepath.Join(dir, "templates/hpa.yaml") + tplname := filepath.Join(dir, "templates", "hpa.yaml") writeFile(tplname, []byte("FOO")) // Now re-run the create diff --git a/pkg/cmd/dependency_build_test.go b/pkg/cmd/dependency_build_test.go index a3473301d..7b3bea28a 100644 --- a/pkg/cmd/dependency_build_test.go +++ b/pkg/cmd/dependency_build_test.go @@ -72,7 +72,7 @@ func TestDependencyBuildCmd(t *testing.T) { } // Make sure the actual file got downloaded. - expect := filepath.Join(rootDir, chartname, "charts/reqtest-0.1.0.tgz") + expect := filepath.Join(rootDir, chartname, "charts", "reqtest-0.1.0.tgz") if _, err := os.Stat(expect); err != nil { t.Fatal(err) } diff --git a/pkg/cmd/repo_add_test.go b/pkg/cmd/repo_add_test.go index 1290d3862..ed677f98a 100644 --- a/pkg/cmd/repo_add_test.go +++ b/pkg/cmd/repo_add_test.go @@ -49,7 +49,7 @@ func TestRepoAddCmd(t *testing.T) { ) defer srv2.Stop() - tmpdir := filepath.Join(t.TempDir(), "path-component.yaml/data") + tmpdir := filepath.Join(t.TempDir(), "path-component.yaml", "data") if err := os.MkdirAll(tmpdir, 0o777); err != nil { t.Fatal(err) } diff --git a/pkg/cmd/repo_remove_test.go b/pkg/cmd/repo_remove_test.go index 79778e75e..3b0072a68 100644 --- a/pkg/cmd/repo_remove_test.go +++ b/pkg/cmd/repo_remove_test.go @@ -171,7 +171,7 @@ func TestRepoRemoveCompletion(t *testing.T) { rootDir := t.TempDir() repoFile := filepath.Join(rootDir, "repositories.yaml") - repoCache := filepath.Join(rootDir, "cache/") + repoCache := filepath.Join(rootDir, "cache") var testRepoNames = []string{"foo", "bar", "baz"} diff --git a/pkg/cmd/search_repo.go b/pkg/cmd/search_repo.go index 9c5b1dd18..348ca12b6 100644 --- a/pkg/cmd/search_repo.go +++ b/pkg/cmd/search_repo.go @@ -382,15 +382,15 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell directive := cobra.ShellCompDirectiveDefault if noFile { - directive = directive | cobra.ShellCompDirectiveNoFileComp + directive |= cobra.ShellCompDirectiveNoFileComp } if noSpace { - directive = directive | cobra.ShellCompDirectiveNoSpace + directive |= cobra.ShellCompDirectiveNoSpace } if !includeFiles { // If we should not include files in the completions, // we should disable file completion - directive = directive | cobra.ShellCompDirectiveNoFileComp + directive |= cobra.ShellCompDirectiveNoFileComp } return completions, directive } diff --git a/pkg/cmd/show_test.go b/pkg/cmd/show_test.go index 5da2626d3..e24b8c442 100644 --- a/pkg/cmd/show_test.go +++ b/pkg/cmd/show_test.go @@ -76,7 +76,6 @@ func TestShowPreReleaseChart(t *testing.T) { outdir, contentTmp, ) - //_, out, err := executeActionCommand(cmd) _, _, err := executeActionCommand(cmd) if err != nil { if tt.fail { diff --git a/pkg/cmd/template_test.go b/pkg/cmd/template_test.go index b43151413..d508a0505 100644 --- a/pkg/cmd/template_test.go +++ b/pkg/cmd/template_test.go @@ -40,7 +40,7 @@ func TestTemplateCmd(t *testing.T) { }, { name: "check values files", - cmd: fmt.Sprintf("template '%s' --values '%s'", chartPath, filepath.Join(chartPath, "/charts/subchartA/values.yaml")), + cmd: fmt.Sprintf("template '%s' --values '%s'", chartPath, filepath.Join(chartPath, "charts", "subchartA", "values.yaml")), golden: "output/template-values-files.txt", }, { diff --git a/pkg/downloader/cache_test.go b/pkg/downloader/cache_test.go index d8a2c3768..49a7adc5d 100644 --- a/pkg/downloader/cache_test.go +++ b/pkg/downloader/cache_test.go @@ -117,6 +117,6 @@ func TestDiskCache_fileName(t *testing.T) { cache := &DiskCache{Root: "/tmp/cache"} key := sha256.Sum256([]byte("some data")) - assert.Equal(t, filepath.Join("/tmp/cache", "13", "1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee.chart"), cache.fileName(key, CacheChart)) - assert.Equal(t, filepath.Join("/tmp/cache", "13", "1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee.prov"), cache.fileName(key, CacheProv)) + assert.Equal(t, filepath.FromSlash("/tmp/cache/13/1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee.chart"), cache.fileName(key, CacheChart)) + assert.Equal(t, filepath.FromSlash("/tmp/cache/13/1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee.prov"), cache.fileName(key, CacheProv)) } diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index b3899d813..a4de2f5e3 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -278,7 +278,7 @@ func TestDownloadTo_TLS(t *testing.T) { getter.WithTLSClientConfig( "", "", - filepath.Join("../../testdata/rootca.crt"), + filepath.FromSlash("../../testdata/rootca.crt"), ), }, } diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 683f96007..e99a826b4 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -764,7 +764,7 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]* } url, err = repo.FindChartInRepoURL(repoURL, name, m.Getters, repo.WithChartVersion(version), repo.WithClientTLS(certFile, keyFile, caFile)) if err == nil { - return url, username, password, false, false, "", "", "", err + return url, username, password, false, false, "", "", "", nil } err = fmt.Errorf("chart %s not found in %s: %w", name, repoURL, err) return url, username, password, false, false, "", "", "", err diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go index 37f79478c..bc541339b 100644 --- a/pkg/engine/funcs_test.go +++ b/pkg/engine/funcs_test.go @@ -28,7 +28,7 @@ import ( ) func TestFuncs(t *testing.T) { - //TODO write tests for failure cases + // TODO write tests for failure cases tests := []struct { tpl, expect string vars any diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index 57bd255b3..d7ba770b3 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -225,7 +225,7 @@ func TestDownload(t *testing.T) { // A different host is provided for the WithURL from the one used for Get u2, _ := url.ParseRequestURI(crossAuthSrv.URL) host := strings.Split(u2.Host, ":") - host[0] = host[0] + "a" + host[0] += "a" u2.Host = strings.Join(host, ":") httpgetter, err = NewHTTPGetter( WithURL(u2.String()), @@ -260,7 +260,7 @@ func TestDownload(t *testing.T) { // A different host is provided for the WithURL from the one used for Get u2, _ = url.ParseRequestURI(crossAuthSrv.URL) host = strings.Split(u2.Host, ":") - host[0] = host[0] + "a" + host[0] += "a" u2.Host = strings.Join(host, ":") httpgetter, err = NewHTTPGetter( WithURL(u2.String()), diff --git a/pkg/helmpath/lazypath_unix_test.go b/pkg/helmpath/lazypath_unix_test.go index 6f8c6c2e8..084758a21 100644 --- a/pkg/helmpath/lazypath_unix_test.go +++ b/pkg/helmpath/lazypath_unix_test.go @@ -38,7 +38,7 @@ func TestDataPath(t *testing.T) { t.Setenv(xdg.DataHomeEnvVar, "/tmp") - expected = filepath.Join("/tmp", appName, testFile) + expected = filepath.FromSlash("/tmp/" + appName + "/" + testFile) assert.Equal(t, expected, lazy.dataPath(testFile)) } @@ -50,7 +50,7 @@ func TestConfigPath(t *testing.T) { t.Setenv(xdg.ConfigHomeEnvVar, "/tmp") - expected = filepath.Join("/tmp", appName, testFile) + expected = filepath.FromSlash("/tmp/" + appName + "/" + testFile) assert.Equal(t, expected, lazy.configPath(testFile)) } @@ -62,7 +62,7 @@ func TestCachePath(t *testing.T) { t.Setenv(xdg.CacheHomeEnvVar, "/tmp") - expected = filepath.Join("/tmp", appName, testFile) + expected = filepath.FromSlash("/tmp/" + appName + "/" + testFile) assert.Equal(t, expected, lazy.cachePath(testFile)) } diff --git a/pkg/kube/ready_test.go b/pkg/kube/ready_test.go index 3b80e95d2..fc3ac9409 100644 --- a/pkg/kube/ready_test.go +++ b/pkg/kube/ready_test.go @@ -213,7 +213,7 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) { wantErr: true, }, { - name: "IsReady Deployments", //TODO fix this one + name: "IsReady Deployments", // TODO fix this one fields: fields{ client: fake.NewClientset(), checkJobs: true, diff --git a/pkg/registry/client_scope_test.go b/pkg/registry/client_scope_test.go index 5f121f048..a790c27a7 100644 --- a/pkg/registry/client_scope_test.go +++ b/pkg/registry/client_scope_test.go @@ -76,7 +76,7 @@ func (suite *RegistryScopeTestSuite) Test_1_Check_Push_Request_Scope() { _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) suite.Require().Error(err, "error pushing good ref because auth server doesn't give proper token") - //check the url that authentication server received + // check the url that authentication server received select { case urlStr := <-requestURL: u, err := url.Parse(urlStr) @@ -122,7 +122,7 @@ func (suite *RegistryScopeTestSuite) Test_2_Check_Pull_Request_Scope() { _, err = suite.RegistryClient.Pull(ref) suite.Require().Error(err, "error pulling a simple chart because auth server doesn't give proper token") - //check the url that authentication server received + // check the url that authentication server received select { case urlStr := <-requestURL: u, err := url.Parse(urlStr) diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index afcf88db5..44af191b1 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -150,7 +150,7 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool, auth string) { suite.Require().NoError(err, "no error finding free port for test auth server") defer ln.Close() - //set test auth server host + // set test auth server host suite.AuthServerHost = ln.Addr().String() config.Auth = configuration.Auth{ diff --git a/pkg/release/v1/util/manifest.go b/pkg/release/v1/util/manifest.go index fa26f6256..4efb6b3e4 100644 --- a/pkg/release/v1/util/manifest.go +++ b/pkg/release/v1/util/manifest.go @@ -64,7 +64,7 @@ func SplitManifests(bigFile string) map[string]string { d = strings.TrimLeftFunc(d, unicode.IsSpace) res[fmt.Sprintf(tpl, count)] = d - count = count + 1 + count++ } return res } diff --git a/pkg/repo/v1/index.go b/pkg/repo/v1/index.go index 15f72891b..839a4cca9 100644 --- a/pkg/repo/v1/index.go +++ b/pkg/repo/v1/index.go @@ -318,7 +318,7 @@ func IndexDirectory(dir, baseURL string) (*IndexFile, error) { if err != nil { return nil, err } - moreArchives, err := filepath.Glob(filepath.Join(dir, "**/*.tgz")) + moreArchives, err := filepath.Glob(filepath.Join(dir, "**", "*.tgz")) if err != nil { return nil, err } diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go index 4b4686b66..6044ce3c6 100644 --- a/pkg/storage/driver/sql_test.go +++ b/pkg/storage/driver/sql_test.go @@ -112,12 +112,14 @@ func TestSQLGet(t *testing.T) { func TestSQLList(t *testing.T) { releases := []*rspb.Release{} - releases = append(releases, releaseStub("key-1", 1, "default", common.StatusUninstalled)) - releases = append(releases, releaseStub("key-2", 1, "default", common.StatusUninstalled)) - releases = append(releases, releaseStub("key-3", 1, "default", common.StatusDeployed)) - releases = append(releases, releaseStub("key-4", 1, "default", common.StatusDeployed)) - releases = append(releases, releaseStub("key-5", 1, "default", common.StatusSuperseded)) - releases = append(releases, releaseStub("key-6", 1, "default", common.StatusSuperseded)) + releases = append(releases, + releaseStub("key-1", 1, "default", common.StatusUninstalled), + releaseStub("key-2", 1, "default", common.StatusUninstalled), + releaseStub("key-3", 1, "default", common.StatusDeployed), + releaseStub("key-4", 1, "default", common.StatusDeployed), + releaseStub("key-5", 1, "default", common.StatusSuperseded), + releaseStub("key-6", 1, "default", common.StatusSuperseded), + ) sqlDriver, mock := newTestFixtureSQL(t) diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index ec57ae311..32f926e08 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -324,7 +324,6 @@ func (d *MaxHistoryMockDriver) Name() string { } func TestMaxHistoryErrorHandling(t *testing.T) { - //func TestStorageRemoveLeastRecentWithError(t *testing.T) { storage := Init(NewMaxHistoryMockDriver(driver.NewMemory())) storage.MaxHistory = 1 diff --git a/pkg/strvals/literal_parser_test.go b/pkg/strvals/literal_parser_test.go index aab8e1b73..6c22a732b 100644 --- a/pkg/strvals/literal_parser_test.go +++ b/pkg/strvals/literal_parser_test.go @@ -422,7 +422,7 @@ func TestParseLiteralNestedLevels(t *testing.T) { for i := 1; i <= MaxNestedNameLevel+2; i++ { tmpStr := fmt.Sprintf("name%d", i) if i <= MaxNestedNameLevel+1 { - tmpStr = tmpStr + "." + tmpStr += "." } keyMultipleNestedLevels.WriteString(tmpStr) } diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index c52e8aed9..d74e46986 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -190,8 +190,6 @@ func (t *parser) key(data map[string]any, nestedNameLevel int) (reterr error) { return err } return fmt.Errorf("key %q has no value", string(k)) - //set(data, string(k), "") - //return err case last == '[': // We are in a list index context, so we need to set an index. i, err := t.keyIndex() diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go index 1b73ae16a..e68bc4e19 100644 --- a/pkg/strvals/parser_test.go +++ b/pkg/strvals/parser_test.go @@ -750,7 +750,7 @@ func TestParseSetNestedLevels(t *testing.T) { for i := 1; i <= MaxNestedNameLevel+2; i++ { tmpStr := fmt.Sprintf("name%d", i) if i <= MaxNestedNameLevel+1 { - tmpStr = tmpStr + "." + tmpStr += "." } keyMultipleNestedLevels.WriteString(tmpStr) } From 16787d65f5091cf5c5e4f2884ef0027ada117465 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 10 Jul 2026 09:11:55 +0200 Subject: [PATCH 420/571] feat(linters): add new revive rules for better code quality Signed-off-by: Matthieu MOREL --- .golangci.yml | 10 +++++ internal/chart/v3/loader/load_test.go | 27 +++++-------- internal/chart/v3/util/save.go | 9 ++--- internal/plugin/installer/local_installer.go | 9 ++--- internal/plugin/runtime_subprocess.go | 6 +-- internal/third_party/dep/fs/fs.go | 31 +++++++-------- pkg/action/action.go | 7 ++-- pkg/action/upgrade.go | 7 ++-- pkg/chart/v2/loader/load_test.go | 19 ++++----- pkg/chart/v2/util/save.go | 9 ++--- pkg/cmd/install.go | 41 ++++++++++---------- pkg/cmd/upgrade.go | 39 +++++++++---------- pkg/downloader/manager.go | 23 +++++------ pkg/registry/client.go | 5 +-- pkg/storage/driver/sql.go | 6 +-- 15 files changed, 113 insertions(+), 135 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 1cd720d47..e839ea6a9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -122,6 +122,16 @@ linters: enable-default-rules: true max-open-files: 2048 rules: + - name: early-return + arguments: + - "preserve-scope" + - name: if-return + - name: indent-error-flow + arguments: + - "preserve-scope" + - name: superfluous-else + arguments: + - "preserve-scope" - name: unnecessary-format - name: var-naming arguments: diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index a493104bf..1c5845da6 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -31,6 +31,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/internal/chart/v3" "helm.sh/helm/v4/pkg/chart/common" "helm.sh/helm/v4/pkg/chart/loader/archive" @@ -100,41 +102,30 @@ func TestBomTestData(t *testing.T) { } archive, err := os.ReadFile("testdata/frobnitz_with_bom.tgz") - if err != nil { - t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) - } + require.NoErrorf(t, err, "Error reading archive frobnitz_with_bom.tgz") unzipped, err := gzip.NewReader(bytes.NewReader(archive)) - if err != nil { - t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) - } + require.NoErrorf(t, err, "Error reading archive frobnitz_with_bom.tgz") defer unzipped.Close() for _, testFile := range testFiles { data := make([]byte, 3) err := unzipped.Reset(bytes.NewReader(archive)) - if err != nil { - t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) - } + require.NoErrorf(t, err, "Error reading archive frobnitz_with_bom.tgz") tr := tar.NewReader(unzipped) for { file, err := tr.Next() if errors.Is(err, io.EOF) { break } - if err != nil { - t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) - } + require.NoErrorf(t, err, "Error reading archive frobnitz_with_bom.tgz") if file != nil && strings.EqualFold(file.Name, testFile) { _, err := tr.Read(data) - if err != nil { - t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) - } else { + if err == nil { break } + t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) } } - if !bytes.Equal(data, utf8bom) { - t.Fatalf("Test file has no BOM or is invalid: frobnitz_with_bom.tgz/%s", testFile) - } + require.Truef(t, bytes.Equal(data, utf8bom), "Test file has no BOM or is invalid: frobnitz_with_bom.tgz/%s", testFile) } } diff --git a/internal/chart/v3/util/save.go b/internal/chart/v3/util/save.go index 942e736ed..09235c1ea 100644 --- a/internal/chart/v3/util/save.go +++ b/internal/chart/v3/util/save.go @@ -114,13 +114,12 @@ func Save(c *chart.Chart, outDir string) (string, error) { filename = filepath.Join(outDir, filename) dir := filepath.Dir(filename) if stat, err := os.Stat(dir); err != nil { - if errors.Is(err, fs.ErrNotExist) { - if err2 := os.MkdirAll(dir, 0o755); err2 != nil { - return "", err2 - } - } else { + if !errors.Is(err, fs.ErrNotExist) { return "", fmt.Errorf("stat %s: %w", dir, err) } + if err2 := os.MkdirAll(dir, 0o755); err2 != nil { + return "", err2 + } } else if !stat.IsDir() { return "", fmt.Errorf("is not a directory: %s", dir) } diff --git a/internal/plugin/installer/local_installer.go b/internal/plugin/installer/local_installer.go index 8d067612f..a7a6caa14 100644 --- a/internal/plugin/installer/local_installer.go +++ b/internal/plugin/installer/local_installer.go @@ -204,14 +204,13 @@ func (i *LocalInstaller) GetVerificationData() (archiveData, provData []byte, fi provFile := i.Source + ".prov" i.provData, err = os.ReadFile(provFile) if err != nil { - if os.IsNotExist(err) { - // If provenance file doesn't exist, set provData to nil - // The verification logic will handle this gracefully - i.provData = nil - } else { + if !os.IsNotExist(err) { // If file exists but can't be read (permissions, etc), return error return nil, nil, "", fmt.Errorf("failed to access provenance file %s: %w", provFile, err) } + // If provenance file doesn't exist, set provData to nil + // The verification logic will handle this gracefully + i.provData = nil } } diff --git a/internal/plugin/runtime_subprocess.go b/internal/plugin/runtime_subprocess.go index 3c0af4a4e..b921587c1 100644 --- a/internal/plugin/runtime_subprocess.go +++ b/internal/plugin/runtime_subprocess.go @@ -125,11 +125,7 @@ func (r *SubprocessPluginRuntime) InvokeWithEnv(main string, argv []string, env cmd.Stdout = stdout cmd.Stderr = stderr - if err := executeCmd(cmd, r.metadata.Name); err != nil { - return err - } - - return nil + return executeCmd(cmd, r.metadata.Name) } func (r *SubprocessPluginRuntime) InvokeHook(event string) error { diff --git a/internal/third_party/dep/fs/fs.go b/internal/third_party/dep/fs/fs.go index 2607c73ac..4a8a736f7 100644 --- a/internal/third_party/dep/fs/fs.go +++ b/internal/third_party/dep/fs/fs.go @@ -156,24 +156,23 @@ func CopyFile(src, dst string) (err error) { if sym, err := IsSymlink(src); err != nil { return fmt.Errorf("symlink check failed: %w", err) } else if sym { - if err := cloneSymlink(src, dst); err != nil { - if runtime.GOOS == "windows" { - // If cloning the symlink fails on Windows because the user - // does not have the required privileges, ignore the error and - // fall back to copying the file contents. - // - // ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522): - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx - lerr := &os.LinkError{} - if errors.As(err, &lerr) && !errors.Is(lerr.Err, syscall.Errno(1314)) { - return err - } - } else { - return err - } - } else { + err := cloneSymlink(src, dst) + if err == nil { return nil } + if runtime.GOOS != "windows" { + return err + } + // If cloning the symlink fails on Windows because the user + // does not have the required privileges, ignore the error and + // fall back to copying the file contents. + // + // ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522): + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx + lerr := &os.LinkError{} + if errors.As(err, &lerr) && !errors.Is(lerr.Err, syscall.Errno(1314)) { + return err + } } in, err := os.Open(src) diff --git a/pkg/action/action.go b/pkg/action/action.go index 8105083f9..bcc91816c 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -551,12 +551,11 @@ func (cfg *Configuration) getCapabilities() (*common.Capabilities, error) { // See https://github.com/kubernetes/kubernetes/issues/72051#issuecomment-521157642 apiVersions, err := GetVersionSet(dc) if err != nil { - if discovery.IsGroupDiscoveryFailedError(err) { - cfg.Logger().Warn("the kubernetes server has an orphaned API service", slog.Any("error", err)) - cfg.Logger().Warn("to fix this, kubectl delete apiservice ") - } else { + if !discovery.IsGroupDiscoveryFailedError(err) { return nil, fmt.Errorf("could not get apiVersions from Kubernetes: %w", err) } + cfg.Logger().Warn("the kubernetes server has an orphaned API service", slog.Any("error", err)) + cfg.Logger().Warn("to fix this, kubectl delete apiservice ") } cfg.Capabilities = &common.Capabilities{ diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 98ac44690..7f66ceefb 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -261,12 +261,11 @@ func (u *Upgrade) prepareUpgrade(ctx context.Context, name string, chart *chartv return nil, nil, false, cerr } if err != nil { - if errors.Is(err, driver.ErrNoDeployedReleases) && - (lastRelease.Info.Status == rcommon.StatusFailed || lastRelease.Info.Status == rcommon.StatusSuperseded) { - currentRelease = lastRelease - } else { + if !errors.Is(err, driver.ErrNoDeployedReleases) || + (lastRelease.Info.Status != rcommon.StatusFailed && lastRelease.Info.Status != rcommon.StatusSuperseded) { return nil, nil, false, err } + currentRelease = lastRelease } } diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index e422c30de..46d17e899 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -31,6 +31,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/chart/common" "helm.sh/helm/v4/pkg/chart/loader/archive" chart "helm.sh/helm/v4/pkg/chart/v2" @@ -111,30 +113,23 @@ func TestBomTestData(t *testing.T) { for _, testFile := range testFiles { data := make([]byte, 3) err := unzipped.Reset(bytes.NewReader(archive)) - if err != nil { - t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) - } + require.NoErrorf(t, err, "Error reading archive frobnitz_with_bom.tgz") tr := tar.NewReader(unzipped) for { file, err := tr.Next() if errors.Is(err, io.EOF) { break } - if err != nil { - t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) - } + require.NoErrorf(t, err, "Error reading archive frobnitz_with_bom.tgz") if file != nil && strings.EqualFold(file.Name, testFile) { _, err := tr.Read(data) - if err != nil { - t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) - } else { + if err == nil { break } + t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) } } - if !bytes.Equal(data, utf8bom) { - t.Fatalf("Test file has no BOM or is invalid: frobnitz_with_bom.tgz/%s", testFile) - } + require.Truef(t, bytes.Equal(data, utf8bom), "Test file has no BOM or is invalid: frobnitz_with_bom.tgz/%s", testFile) } } diff --git a/pkg/chart/v2/util/save.go b/pkg/chart/v2/util/save.go index d8189c8bf..5444ca218 100644 --- a/pkg/chart/v2/util/save.go +++ b/pkg/chart/v2/util/save.go @@ -114,13 +114,12 @@ func Save(c *chart.Chart, outDir string) (string, error) { filename = filepath.Join(outDir, filename) dir := filepath.Dir(filename) if stat, err := os.Stat(dir); err != nil { - if errors.Is(err, fs.ErrNotExist) { - if err2 := os.MkdirAll(dir, 0o755); err2 != nil { - return "", err2 - } - } else { + if !errors.Is(err, fs.ErrNotExist) { return "", fmt.Errorf("stat %s: %w", dir, err) } + if err2 := os.MkdirAll(dir, 0o755); err2 != nil { + return "", err2 + } } else if !stat.IsDir() { return "", fmt.Errorf("is not a directory: %s", dir) } diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index 832968ff0..c7aa2c542 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -301,29 +301,28 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options // As of Helm 2.4.0, this is treated as a stopping condition: // https://github.com/helm/helm/issues/2209 if err := action.CheckDependencies(chartRequested, req); err != nil { - if client.DependencyUpdate { - man := &downloader.Manager{ - Out: out, - ChartPath: cp, - Keyring: client.Keyring, - SkipUpdate: false, - Getters: p, - RepositoryConfig: settings.RepositoryConfig, - RepositoryCache: settings.RepositoryCache, - ContentCache: settings.ContentCache, - Debug: settings.Debug, - RegistryClient: client.GetRegistryClient(), - } - if err := man.Update(); err != nil { - return nil, err - } - // Reload the chart with the updated Chart.lock file. - if chartRequested, err = loader.Load(cp); err != nil { - return nil, fmt.Errorf("failed reloading chart after repo update: %w", err) - } - } else { + if !client.DependencyUpdate { return nil, fmt.Errorf("an error occurred while checking for chart dependencies. You may need to run 'helm dependency build' to fetch missing dependencies: %w", err) } + man := &downloader.Manager{ + Out: out, + ChartPath: cp, + Keyring: client.Keyring, + SkipUpdate: false, + Getters: p, + RepositoryConfig: settings.RepositoryConfig, + RepositoryCache: settings.RepositoryCache, + ContentCache: settings.ContentCache, + Debug: settings.Debug, + RegistryClient: client.GetRegistryClient(), + } + if err := man.Update(); err != nil { + return nil, err + } + // Reload the chart with the updated Chart.lock file. + if chartRequested, err = loader.Load(cp); err != nil { + return nil, fmt.Errorf("failed reloading chart after repo update: %w", err) + } } } diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index 9ec4f4ef2..a6aa96129 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -206,28 +206,27 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if req := ac.MetaDependencies(); len(req) > 0 { if err := action.CheckDependencies(ch, req); err != nil { err = fmt.Errorf("an error occurred while checking for chart dependencies. You may need to run 'helm dependency build' to fetch missing dependencies: %w", err) - if client.DependencyUpdate { - man := &downloader.Manager{ - Out: out, - ChartPath: chartPath, - Keyring: client.Keyring, - SkipUpdate: false, - Getters: p, - RepositoryConfig: settings.RepositoryConfig, - RepositoryCache: settings.RepositoryCache, - ContentCache: settings.ContentCache, - Debug: settings.Debug, - } - if err := man.Update(); err != nil { - return err - } - // Reload the chart with the updated Chart.lock file. - if ch, err = loader.Load(chartPath); err != nil { - return fmt.Errorf("failed reloading chart after repo update: %w", err) - } - } else { + if !client.DependencyUpdate { return err } + man := &downloader.Manager{ + Out: out, + ChartPath: chartPath, + Keyring: client.Keyring, + SkipUpdate: false, + Getters: p, + RepositoryConfig: settings.RepositoryConfig, + RepositoryCache: settings.RepositoryCache, + ContentCache: settings.ContentCache, + Debug: settings.Debug, + } + if err := man.Update(); err != nil { + return err + } + // Reload the chart with the updated Chart.lock file. + if ch, err = loader.Load(chartPath); err != nil { + return fmt.Errorf("failed reloading chart after repo update: %w", err) + } } } diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 683f96007..e5c015f24 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -118,16 +118,15 @@ func (m *Manager) Build() error { } if sum, err := resolver.HashReq(req, lock.Dependencies); err != nil || sum != lock.Digest { + if c.Metadata.APIVersion != chart.APIVersionV1 { + return errors.New("the lock file (Chart.lock) is out of sync with the dependencies file (Chart.yaml). Please update the dependencies with 'helm dependency update'") + } // If lock digest differs and chart is apiVersion v1, it maybe because the lock was built // with Helm 2 and therefore should be checked with Helm v2 hash // Fix for: https://github.com/helm/helm/issues/7233 - if c.Metadata.APIVersion == chart.APIVersionV1 { - log.Println("warning: a valid Helm v3 hash was not found. Checking against Helm v2 hash...") - if v2Sum != lock.Digest { - return errors.New("the lock file (requirements.lock) is out of sync with the dependencies file (requirements.yaml). Please update the dependencies") - } - } else { - return errors.New("the lock file (Chart.lock) is out of sync with the dependencies file (Chart.yaml). Please update the dependencies with 'helm dependency update'") + log.Println("warning: a valid Helm v3 hash was not found. Checking against Helm v2 hash...") + if v2Sum != lock.Digest { + return errors.New("the lock file (requirements.lock) is out of sync with the dependencies file (requirements.yaml). Please update the dependencies") } } @@ -365,16 +364,12 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { } // TODO: this should probably be refactored to be a []error, so we can capture and provide more information rather than "last error wins". - if saveError == nil { - // now we can move all downloaded charts to destPath and delete outdated dependencies - if err := m.safeMoveDeps(deps, tmpPath, destPath); err != nil { - return err - } - } else { + if saveError != nil { fmt.Fprintln(m.Out, "Save error occurred: ", saveError) return saveError } - return nil + // now we can move all downloaded charts to destPath and delete outdated dependencies + return m.safeMoveDeps(deps, tmpPath, destPath) } func parseOCIRef(chartRef string) (string, string, error) { diff --git a/pkg/registry/client.go b/pkg/registry/client.go index a14afdd1f..7a1c8f449 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -484,12 +484,11 @@ func (c *Client) processChartPull(genericResult *GenericPullResult, operation *p var provMissing bool if operation.withProv && provDescriptor == nil { - if operation.ignoreMissingProv { - provMissing = true - } else { + if !operation.ignoreMissingProv { return nil, fmt.Errorf("manifest does not contain a layer with mediatype %s", ProvLayerMediaType) } + provMissing = true } // Build chart-specific result diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go index f3dd053a2..88d24123d 100644 --- a/pkg/storage/driver/sql.go +++ b/pkg/storage/driver/sql.go @@ -404,12 +404,12 @@ func (s *SQL) Query(labels map[string]string) ([]release.Releaser, error) { } sort.Strings(keys) for _, key := range keys { - if _, ok := labelMap[key]; ok { - sb = sb.Where(sq.Eq{key: labels[key]}) - } else { + _, ok := labelMap[key] + if !ok { s.Logger().Debug("unknown label", "key", key) return nil, fmt.Errorf("unknown label %s", key) } + sb = sb.Where(sq.Eq{key: labels[key]}) } // If a namespace was specified, we only list releases from that namespace From a91e9f6a249533cfd8fdb98cbd909ecb16bbc813 Mon Sep 17 00:00:00 2001 From: Akanksha Trehun <146705736+magic-peach@users.noreply.github.com> Date: Fri, 10 Jul 2026 17:00:53 +0530 Subject: [PATCH 421/571] fix(Makefile): update outdated .sha256 comment for Helm v4 (#32323) Signed-off-by: Akanksha Trehun --- Makefile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 81b149a68..5a9c792bb 100644 --- a/Makefile +++ b/Makefile @@ -200,11 +200,10 @@ sign: # The contents of the .sha256sum file are compatible with tools like # shasum. For example, using the following command will verify -# the file helm-3.1.0-rc.1-darwin-amd64.tar.gz: -# shasum -a 256 -c helm-3.1.0-rc.1-darwin-amd64.tar.gz.sha256sum +# the file helm-4.0.0-darwin-amd64.tar.gz: +# shasum -a 256 -c helm-4.0.0-darwin-amd64.tar.gz.sha256sum # The .sha256 files hold only the hash and are not compatible with -# verification tools like shasum or sha256sum. This method and file can be -# removed in Helm v4. +# verification tools like shasum or sha256sum. .PHONY: checksum checksum: for f in $$(ls _dist/*.{gz,zip} 2>/dev/null) ; do \ From cfa3b24fe6c2a0e4bf22887cc1051d7674c6b348 Mon Sep 17 00:00:00 2001 From: Akanksha Trehun <146705736+magic-peach@users.noreply.github.com> Date: Fri, 10 Jul 2026 18:02:08 +0530 Subject: [PATCH 422/571] fix: remove trailing whitespace in .golangci.yml (#32325) Signed-off-by: Akanksha Trehun --- .golangci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 1cd720d47..b820e25ae 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -47,7 +47,7 @@ linters: - whitespace exclusions: - + generated: lax presets: From 7b9a5c8911c7aa53eab1bfde995c63ff626a7ca4 Mon Sep 17 00:00:00 2001 From: Akanksha Trehun <146705736+magic-peach@users.noreply.github.com> Date: Fri, 10 Jul 2026 18:05:23 +0530 Subject: [PATCH 423/571] fix: add missing https:// to Oracle link in ADOPTERS.md (#32324) Signed-off-by: Akanksha Trehun --- ADOPTERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index a83519fea..4efc14e1c 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -12,7 +12,7 @@ - [New Relic](https://www.newrelic.com) - [Octopus Deploy](https://octopus.com/) - [Omnistrate](https://omnistrate.com) -- [Oracle](www.oracle.com) +- [Oracle](https://www.oracle.com) - [Percona](https://www.percona.com) - [Qovery](https://www.qovery.com/) - [Samsung SDS](https://www.samsungsds.com/) From bfebbb7c1a3cea951dcc33d3253492f6a6df4eb2 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Fri, 10 Jul 2026 16:32:48 +0100 Subject: [PATCH 424/571] Fix vanishing empty lines Signed-off-by: Matheus Pimenta --- internal/release/v2/util/manifest.go | 2 +- internal/release/v2/util/manifest_test.go | 32 ++++++++++++------- pkg/action/action_test.go | 15 +++++---- pkg/action/testdata/rbac.txt | 2 ++ pkg/cmd/testdata/output/object-order.txt | 26 +++++++++++++++ .../output/template-show-only-glob.txt | 4 +++ .../output/template-show-only-multiple.txt | 3 ++ .../output/template-show-only-one.txt | 1 + pkg/release/v1/util/manifest.go | 2 +- pkg/release/v1/util/manifest_test.go | 32 ++++++++++++------- 10 files changed, 89 insertions(+), 30 deletions(-) diff --git a/internal/release/v2/util/manifest.go b/internal/release/v2/util/manifest.go index 5dbcdaea5..d5698f468 100644 --- a/internal/release/v2/util/manifest.go +++ b/internal/release/v2/util/manifest.go @@ -34,7 +34,7 @@ type SimpleHead struct { } `json:"metadata,omitempty"` } -var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") +var sep = regexp.MustCompile(`(?m)^---[ \t]*`) // SplitManifests takes a manifest string and returns a map containing individual manifests. // diff --git a/internal/release/v2/util/manifest_test.go b/internal/release/v2/util/manifest_test.go index 463ab5a4d..1806368af 100644 --- a/internal/release/v2/util/manifest_test.go +++ b/internal/release/v2/util/manifest_test.go @@ -74,7 +74,7 @@ spec: name: "whitespace-only doc after separator is skipped", input: "---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1\n---\n \n", expected: map[string]string{ - "manifest-0": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1", + "manifest-0": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1\n", }, }, { @@ -110,7 +110,8 @@ metadata: "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -329,8 +330,8 @@ data: }, }, - // Multi-doc with block scalars: the regex consumes \s*\n before ---, - // so trailing newlines from non-last docs are stripped. + // Multi-doc with block scalars: the separator regex preserves trailing + // newlines from non-last documents. { name: "multi-doc block scalar clip (|) before separator", input: ` @@ -354,7 +355,8 @@ metadata: name: test data: key: | - hello`, + hello +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -387,7 +389,10 @@ metadata: name: test data: key: |+ - hello`, + hello + + +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -432,7 +437,8 @@ metadata: "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -460,11 +466,13 @@ metadata: "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: - name: cm2`, + name: cm2 +`, "manifest-2": `apiVersion: v1 kind: ConfigMap metadata: @@ -493,11 +501,13 @@ metadata: "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: - name: cm2`, + name: cm2 +`, "manifest-2": `apiVersion: v1 kind: ConfigMap metadata: diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index 4ad08b761..ae59db4eb 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -1229,7 +1229,7 @@ metadata: annotations: postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' data: - key: |- + key: | hello --- apiVersion: v1 @@ -1269,7 +1269,7 @@ metadata: annotations: postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' data: - key: |- + key: | hello --- apiVersion: v1 @@ -1310,7 +1310,7 @@ metadata: annotations: postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' data: - key: |- + key: | hello --- apiVersion: v1 @@ -1473,7 +1473,7 @@ metadata: annotations: postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' data: - key: |- + key: | hello --- apiVersion: v1 @@ -1513,8 +1513,9 @@ metadata: annotations: postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' data: - key: |- + key: |+ hello + --- apiVersion: v1 kind: ConfigMap @@ -1554,8 +1555,10 @@ metadata: annotations: postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml' data: - key: |- + key: |+ hello + + --- apiVersion: v1 kind: ConfigMap diff --git a/pkg/action/testdata/rbac.txt b/pkg/action/testdata/rbac.txt index 91938d5cc..c8c2fd66e 100644 --- a/pkg/action/testdata/rbac.txt +++ b/pkg/action/testdata/rbac.txt @@ -8,6 +8,8 @@ rules: - apiGroups: [""] resources: ["pods", "pods/exec", "pods/log"] verbs: ["*"] + + --- # Source: hello/templates/rbac apiVersion: rbac.authorization.k8s.io/v1 diff --git a/pkg/cmd/testdata/output/object-order.txt b/pkg/cmd/testdata/output/object-order.txt index 1ff39f33c..96e3ba053 100644 --- a/pkg/cmd/testdata/output/object-order.txt +++ b/pkg/cmd/testdata/output/object-order.txt @@ -10,6 +10,8 @@ spec: policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/01-a.yml # 2 @@ -22,6 +24,8 @@ spec: policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/01-a.yml # 3 @@ -34,6 +38,8 @@ spec: policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 5 @@ -46,6 +52,8 @@ spec: policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 7 @@ -58,6 +66,8 @@ spec: policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 8 @@ -70,6 +80,8 @@ spec: policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 9 @@ -82,6 +94,8 @@ spec: policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 10 @@ -94,6 +108,8 @@ spec: policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 11 @@ -106,6 +122,8 @@ spec: policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 12 @@ -118,6 +136,8 @@ spec: policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 13 @@ -130,6 +150,8 @@ spec: policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 14 @@ -142,6 +164,8 @@ spec: policyTypes: - Egress - Ingress + + --- # Source: object-order/templates/02-b.yml # 15 (11th object within 02-b.yml, in order to test `SplitManifests` which assigns `manifest-10` @@ -190,3 +214,5 @@ spec: policyTypes: - Egress - Ingress + + diff --git a/pkg/cmd/testdata/output/template-show-only-glob.txt b/pkg/cmd/testdata/output/template-show-only-glob.txt index b2d2b1c2d..160f08a06 100644 --- a/pkg/cmd/testdata/output/template-show-only-glob.txt +++ b/pkg/cmd/testdata/output/template-show-only-glob.txt @@ -8,6 +8,8 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -22,3 +24,5 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + + diff --git a/pkg/cmd/testdata/output/template-show-only-multiple.txt b/pkg/cmd/testdata/output/template-show-only-multiple.txt index 1aac3081a..3f9aa6f2f 100644 --- a/pkg/cmd/testdata/output/template-show-only-multiple.txt +++ b/pkg/cmd/testdata/output/template-show-only-multiple.txt @@ -19,6 +19,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchart + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -36,3 +37,5 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + + diff --git a/pkg/cmd/testdata/output/template-show-only-one.txt b/pkg/cmd/testdata/output/template-show-only-one.txt index 9cc34f515..27a56c03c 100644 --- a/pkg/cmd/testdata/output/template-show-only-one.txt +++ b/pkg/cmd/testdata/output/template-show-only-one.txt @@ -19,3 +19,4 @@ spec: name: nginx selector: app.kubernetes.io/name: subchart + diff --git a/pkg/release/v1/util/manifest.go b/pkg/release/v1/util/manifest.go index fa26f6256..f76d006cd 100644 --- a/pkg/release/v1/util/manifest.go +++ b/pkg/release/v1/util/manifest.go @@ -34,7 +34,7 @@ type SimpleHead struct { } `json:"metadata,omitempty"` } -var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") +var sep = regexp.MustCompile(`(?m)^---[ \t]*`) // SplitManifests takes a manifest string and returns a map containing individual manifests. // diff --git a/pkg/release/v1/util/manifest_test.go b/pkg/release/v1/util/manifest_test.go index d1efa21e1..773723c86 100644 --- a/pkg/release/v1/util/manifest_test.go +++ b/pkg/release/v1/util/manifest_test.go @@ -74,7 +74,7 @@ spec: name: "whitespace-only doc after separator is skipped", input: "---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1\n---\n \n", expected: map[string]string{ - "manifest-0": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1", + "manifest-0": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1\n", }, }, { @@ -110,7 +110,8 @@ metadata: "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -329,8 +330,8 @@ data: }, }, - // Multi-doc with block scalars: the regex consumes \s*\n before ---, - // so trailing newlines from non-last docs are stripped. + // Multi-doc with block scalars: the separator regex preserves trailing + // newlines from non-last documents. { name: "multi-doc block scalar clip (|) before separator", input: ` @@ -354,7 +355,8 @@ metadata: name: test data: key: | - hello`, + hello +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -387,7 +389,10 @@ metadata: name: test data: key: |+ - hello`, + hello + + +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -432,7 +437,8 @@ metadata: "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: @@ -460,11 +466,13 @@ metadata: "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: - name: cm2`, + name: cm2 +`, "manifest-2": `apiVersion: v1 kind: ConfigMap metadata: @@ -493,11 +501,13 @@ metadata: "manifest-0": `apiVersion: v1 kind: ConfigMap metadata: - name: cm1`, + name: cm1 +`, "manifest-1": `apiVersion: v1 kind: ConfigMap metadata: - name: cm2`, + name: cm2 +`, "manifest-2": `apiVersion: v1 kind: ConfigMap metadata: From 73c1fb245120df3f3adb53c12e69f29134147de7 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Fri, 10 Jul 2026 22:34:56 +0100 Subject: [PATCH 425/571] Address review comments Signed-off-by: Matheus Pimenta --- pkg/action/action_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index ae59db4eb..880a9930b 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -1198,9 +1198,9 @@ data: `, }, - // Multi-doc tests: block scalar doc is NOT the last document. - // SplitManifests' regex consumes \s*\n before ---, so trailing - // newlines from non-last docs are always stripped. + // Multi-doc block scalar tests where the block scalar document is NOT the last: + // the separator regex does not consume trailing newlines, so YAML chomping + // indicators (|, |+, |-) are respected. // | (clip) in multi-doc (first doc) { From 84e63e5c38913594e476b15609fb6c7ab2d60467 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 Jul 2026 08:08:39 -0600 Subject: [PATCH 426/571] chore(deps): bump oras.land/oras-go/v2 from 2.6.1 to 2.6.2 (#32333) Bumps [oras.land/oras-go/v2](https://github.com/oras-project/oras-go) from 2.6.1 to 2.6.2. - [Release notes](https://github.com/oras-project/oras-go/releases) - [Commits](https://github.com/oras-project/oras-go/compare/v2.6.1...v2.6.2) --- updated-dependencies: - dependency-name: oras.land/oras-go/v2 dependency-version: 2.6.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b3c6eb290..b2b3d6ca0 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( k8s.io/client-go v0.36.2 k8s.io/klog/v2 v2.140.0 k8s.io/kubectl v0.36.2 - oras.land/oras-go/v2 v2.6.1 + oras.land/oras-go/v2 v2.6.2 sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/kustomize/kyaml v0.21.1 sigs.k8s.io/yaml v1.6.0 diff --git a/go.sum b/go.sum index 06539321b..323cd5c1f 100644 --- a/go.sum +++ b/go.sum @@ -509,8 +509,8 @@ k8s.io/kubectl v0.36.2 h1:rpUGGpeL09XVOLep2yle5jrtk//JA1L6ZHfkQQtVEwk= k8s.io/kubectl v0.36.2/go.mod h1:gVbQ3B/yb4bSR2ggQ7rd0W6icUSWs7sduH4e16Vii+0= k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc= k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= -oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk= +oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= +oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4= sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= From dad026acb8caab81411514715c50e50510f558ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 Jul 2026 08:09:17 -0600 Subject: [PATCH 427/571] chore(deps): bump github.com/mattn/go-shellwords from 1.0.13 to 1.0.14 (#32334) Bumps [github.com/mattn/go-shellwords](https://github.com/mattn/go-shellwords) from 1.0.13 to 1.0.14. - [Commits](https://github.com/mattn/go-shellwords/compare/v1.0.13...v1.0.14) --- updated-dependencies: - dependency-name: github.com/mattn/go-shellwords dependency-version: 1.0.14 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b2b3d6ca0..016b0fded 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/gosuri/uitable v0.0.4 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.12.3 - github.com/mattn/go-shellwords v1.0.13 + github.com/mattn/go-shellwords v1.0.14 github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 diff --git a/go.sum b/go.sum index 323cd5c1f..d097bc72c 100644 --- a/go.sum +++ b/go.sum @@ -205,8 +205,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-shellwords v1.0.13 h1:DC0OMEpGjm6LfNFU4ckYcvbQKyp2vE8atyFGXNtDcf4= -github.com/mattn/go-shellwords v1.0.13/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-shellwords v1.0.14 h1:yUKzIgsCnosndOASY6/enly1EAuaXeFSQ7cdyA3OuYg= +github.com/mattn/go-shellwords v1.0.14/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= From fb53c00ae72c08cf6d64289efde3aaeeda6c42fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 Jul 2026 08:09:20 -0600 Subject: [PATCH 428/571] chore(deps): bump actions/stale from 10.3.0 to 10.4.0 (#32330) Bumps [actions/stale](https://github.com/actions/stale) from 10.3.0 to 10.4.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899...1e223db275d687790206a7acac4d1a11bd6fe629) --- updated-dependencies: - dependency-name: actions/stale dependency-version: 10.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index bbe339e79..678952b03 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -12,7 +12,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 + - uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.' From b963afaddfe127620c70773e46b1d3a1f9c559e8 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sat, 11 Jul 2026 09:06:28 -0600 Subject: [PATCH 429/571] ci: track GitHub Actions updates on dev-v3 via Dependabot Dependabot reads its config only from the default branch, and the github-actions ecosystem was configured for main only. As a result, Action version bumps never flowed to dev-v3, leaving its workflows (e.g. govulncheck-action) frozen and drifting from main. Add a github-actions update block targeting dev-v3 so Action bumps are opened there too, matching the existing gomod/dev-v3 arrangement. Signed-off-by: Terry Howe --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0133fd8f4..d8e8e3379 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -37,3 +37,9 @@ updates: directory: "/" schedule: interval: "daily" + - # Keep dev-v3 GitHub Actions up to date, while Helm v3 is within support + package-ecosystem: "github-actions" + target-branch: "dev-v3" + directory: "/" + schedule: + interval: "daily" From 68977ec0b5a446286668170dd72bdf59695f8cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=80=E5=90=89?= <84045975+thc1006@users.noreply.github.com> Date: Sun, 12 Jul 2026 03:25:44 +0800 Subject: [PATCH 430/571] fix(ci): pin govulncheck-action to the v1.1.0 release (#32304) golang/govulncheck-action published v1.1.0 on 2026-07-10, tagging the master commit (032d455) the govulncheck step was already pinned to. Update the pin comment to # pin@v1.1.0 so it matches the repo's pin@ convention and Dependabot can track the action by semver again. The pinned SHA is unchanged; v1.1.0 points to that commit, so CI behavior is identical and only the trailing comment changes. Closes: #32301 Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com> --- .github/workflows/govulncheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 5e5111432..c7c9b3d53 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -30,6 +30,6 @@ jobs: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true - name: govulncheck - uses: golang/govulncheck-action@032d45514ae346b1db93c04b0c90b841c370344f # pin@032d455 (from main 2026-07-06) + uses: golang/govulncheck-action@032d45514ae346b1db93c04b0c90b841c370344f # pin@v1.1.0 with: go-package: ./... From d72b28ebcbecf1c582e57f17c1272536b90513d1 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 10 Jul 2026 00:24:52 +0200 Subject: [PATCH 431/571] chore(internal): refactor: convert tests to testify assert/require part 1 refactor: convert tests to testify assert/require in internal/chart/v3/lint/rules Signed-off-by: Matthieu MOREL --- .../chart/v3/lint/rules/chartfile_test.go | 156 ++++----- .../chart/v3/lint/rules/deprecations_test.go | 25 +- internal/chart/v3/util/dependencies_test.go | 300 +++++------------- internal/plugin/metadata_test.go | 32 +- internal/plugin/verify_test.go | 113 +++---- 5 files changed, 200 insertions(+), 426 deletions(-) diff --git a/internal/chart/v3/lint/rules/chartfile_test.go b/internal/chart/v3/lint/rules/chartfile_test.go index a7669a0aa..9ac4ce75e 100644 --- a/internal/chart/v3/lint/rules/chartfile_test.go +++ b/internal/chart/v3/lint/rules/chartfile_test.go @@ -18,11 +18,15 @@ package rules import ( "errors" + "fmt" "os" "path/filepath" - "strings" + "strconv" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/internal/chart/v3" "helm.sh/helm/v4/internal/chart/v3/lint/support" chartutil "helm.sh/helm/v4/internal/chart/v3/util" @@ -49,33 +53,23 @@ func TestValidateChartYamlNotDirectory(t *testing.T) { defer os.Remove(nonExistingChartFilePath) err := validateChartYamlNotDirectory(nonExistingChartFilePath) - if err == nil { - t.Error("validateChartYamlNotDirectory to return a linter error, got no error") - } + assert.Error(t, err, "validateChartYamlNotDirectory to return a linter error, got no error") } func TestValidateChartYamlFormat(t *testing.T) { err := validateChartYamlFormat(errors.New("Read error")) - if err == nil { - t.Error("validateChartYamlFormat to return a linter error, got no error") - } + require.Error(t, err, "validateChartYamlFormat to return a linter error, got no error") err = validateChartYamlFormat(nil) - if err != nil { - t.Error("validateChartYamlFormat to return no error, got a linter error") - } + assert.NoError(t, err, "validateChartYamlFormat to return no error, got a linter error") } func TestValidateChartName(t *testing.T) { err := validateChartName(badChart) - if err == nil { - t.Error("validateChartName to return a linter error, got no error") - } + require.Error(t, err, "validateChartName to return a linter error, got no error") err = validateChartName(badChartName) - if err == nil { - t.Error("expected validateChartName to return a linter error for an invalid name, got no error") - } + assert.Error(t, err, "expected validateChartName to return a linter error for an invalid name, got no error") } func TestValidateChartVersion(t *testing.T) { @@ -93,20 +87,18 @@ func TestValidateChartVersion(t *testing.T) { var successTest = []string{"0.0.1", "0.0.1+build", "0.0.1-beta"} - for _, test := range failTest { - badChart.Version = test.Version - err := validateChartVersion(badChart) - if err == nil || !strings.Contains(err.Error(), test.ErrorMsg) { - t.Errorf("validateChartVersion(%s) to return \"%s\", got no error", test.Version, test.ErrorMsg) - } + for i, test := range failTest { + t.Run(strconv.Itoa(i), func(t *testing.T) { + badChart.Version = test.Version + err := validateChartVersion(badChart) + require.ErrorContains(t, err, test.ErrorMsg, "validateChartVersion(%s) to return \"%s\", got no error", test.Version, test.ErrorMsg) + }) } for _, version := range successTest { badChart.Version = version err := validateChartVersion(badChart) - if err != nil { - t.Errorf("validateChartVersion(%s) to return no error, got a linter error", version) - } + assert.NoError(t, err, "validateChartVersion(%s) to return no error, got a linter error", version) } } @@ -130,49 +122,43 @@ func TestValidateChartMaintainer(t *testing.T) { } for _, test := range failTest { - badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}} - err := validateChartMaintainer(badChart) - if err == nil || !strings.Contains(err.Error(), test.ErrorMsg) { - t.Errorf("validateChartMaintainer(%s, %s) to return \"%s\", got no error", test.Name, test.Email, test.ErrorMsg) - } + t.Run(fmt.Sprintf("%s, %s", test.Name, test.Email), func(t *testing.T) { + badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}} + err := validateChartMaintainer(badChart) + require.ErrorContains(t, err, test.ErrorMsg, "validateChartMaintainer(%s, %s) to return \"%s\", got no error", test.Name, test.Email, test.ErrorMsg) + }) } for _, test := range successTest { - badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}} - err := validateChartMaintainer(badChart) - if err != nil { - t.Errorf("validateChartMaintainer(%s, %s) to return no error, got %s", test.Name, test.Email, err.Error()) - } + t.Run(fmt.Sprintf("%s, %s", test.Name, test.Email), func(t *testing.T) { + badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}} + err := validateChartMaintainer(badChart) + require.NoError(t, err, "validateChartMaintainer(%s, %s) to return no error", test.Name, test.Email) + }) } // Testing for an empty maintainer badChart.Maintainers = []*chart.Maintainer{nil} err := validateChartMaintainer(badChart) - if err == nil { - t.Error("validateChartMaintainer did not return error for nil maintainer as expected") - } - if err.Error() != "a maintainer entry is empty" { - t.Errorf("validateChartMaintainer returned unexpected error for nil maintainer: %s", err.Error()) - } + require.Error(t, err, "validateChartMaintainer did not return error for nil maintainer as expected") + assert.EqualError(t, err, "a maintainer entry is empty", "validateChartMaintainer returned unexpected error for nil maintainer") } func TestValidateChartSources(t *testing.T) { var failTest = []string{"", "RiverRun", "john@winterfell", "riverrun.io"} var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"} for _, test := range failTest { - badChart.Sources = []string{test} - err := validateChartSources(badChart) - if err == nil || !strings.Contains(err.Error(), "invalid source URL") { - t.Errorf("validateChartSources(%s) to return \"invalid source URL\", got no error", test) - } + t.Run(test, func(t *testing.T) { + badChart.Sources = []string{test} + err := validateChartSources(badChart) + require.ErrorContains(t, err, "invalid source URL", "validateChartSources(%s) to return \"invalid source URL\", got no error", test) + }) } for _, test := range successTest { badChart.Sources = []string{test} err := validateChartSources(badChart) - if err != nil { - t.Errorf("validateChartSources(%s) to return no error, got %s", test, err.Error()) - } + assert.NoError(t, err, "validateChartSources(%s) to return no error", test) } } @@ -184,11 +170,8 @@ func TestValidateChartIconPresence(t *testing.T) { err := validateChartIconPresence(testChart) - if err == nil { - t.Error("validateChartIconPresence to return a linter error, got no error") - } else if !strings.Contains(err.Error(), "icon is recommended") { - t.Errorf("expected %q, got %q", "icon is recommended", err.Error()) - } + require.Error(t, err, "validateChartIconPresence to return a linter error, got no error") + assert.ErrorContains(t, err, "icon is recommended", "expected %q", "icon is recommended") }) t.Run("Icon present", func(t *testing.T) { testChart := &chart.Metadata{ @@ -197,9 +180,7 @@ func TestValidateChartIconPresence(t *testing.T) { err := validateChartIconPresence(testChart) - if err != nil { - t.Errorf("Unexpected error: %q", err.Error()) - } + assert.NoError(t, err, "Unexpected error") }) } @@ -207,19 +188,17 @@ func TestValidateChartIconURL(t *testing.T) { var failTest = []string{"RiverRun", "john@winterfell", "riverrun.io"} var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish.png"} for _, test := range failTest { - badChart.Icon = test - err := validateChartIconURL(badChart) - if err == nil || !strings.Contains(err.Error(), "invalid icon URL") { - t.Errorf("validateChartIconURL(%s) to return \"invalid icon URL\", got no error", test) - } + t.Run(test, func(t *testing.T) { + badChart.Icon = test + err := validateChartIconURL(badChart) + require.ErrorContains(t, err, "invalid icon URL", "validateChartIconURL(%s) to return \"invalid icon URL\", got no error", test) + }) } for _, test := range successTest { badChart.Icon = test - err := validateChartSources(badChart) - if err != nil { - t.Errorf("validateChartIconURL(%s) to return no error, got %s", test, err.Error()) - } + err := validateChartIconURL(badChart) + assert.NoError(t, err, "validateChartIconURL(%s) to return no error", test) } } @@ -230,26 +209,11 @@ func TestV3Chartfile(t *testing.T) { msgs := linter.Messages expectedNumberOfErrorMessages := 6 - if len(msgs) != expectedNumberOfErrorMessages { - t.Errorf("Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs)) - return - } - - if !strings.Contains(msgs[0].Err.Error(), "name is required") { - t.Errorf("Unexpected message 0: %s", msgs[0].Err) - } - - if !strings.Contains(msgs[1].Err.Error(), "apiVersion is required. The value must be \"v3\"") { - t.Errorf("Unexpected message 1: %s", msgs[1].Err) - } - - if !strings.Contains(msgs[2].Err.Error(), "version '0.0.0.0' is not a valid SemVer") { - t.Errorf("Unexpected message 2: %s", msgs[2].Err) - } - - if !strings.Contains(msgs[3].Err.Error(), "icon is recommended") { - t.Errorf("Unexpected message 3: %s", msgs[3].Err) - } + require.Lenf(t, msgs, expectedNumberOfErrorMessages, "Expected %d errors", expectedNumberOfErrorMessages) + require.ErrorContains(t, msgs[0].Err, "name is required", "Unexpected message 0") + require.ErrorContains(t, msgs[1].Err, "apiVersion is required. The value must be \"v3\"", "Unexpected message 1") + require.ErrorContains(t, msgs[2].Err, "version '0.0.0.0' is not a valid SemVer", "Unexpected message 2") + assert.ErrorContains(t, msgs[3].Err, "icon is recommended", "Unexpected message 3") }) t.Run("Chart.yaml validity issues due to type mismatch", func(t *testing.T) { @@ -258,21 +222,9 @@ func TestV3Chartfile(t *testing.T) { msgs := linter.Messages expectedNumberOfErrorMessages := 3 - if len(msgs) != expectedNumberOfErrorMessages { - t.Errorf("Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs)) - return - } - - if !strings.Contains(msgs[0].Err.Error(), "version should be of type string") { - t.Errorf("Unexpected message 0: %s", msgs[0].Err) - } - - if !strings.Contains(msgs[1].Err.Error(), "version '7.2445e+06' is not a valid SemVer") { - t.Errorf("Unexpected message 1: %s", msgs[1].Err) - } - - if !strings.Contains(msgs[2].Err.Error(), "appVersion should be of type string") { - t.Errorf("Unexpected message 2: %s", msgs[2].Err) - } + require.Lenf(t, msgs, expectedNumberOfErrorMessages, "Expected %d errors", expectedNumberOfErrorMessages) + require.ErrorContains(t, msgs[0].Err, "version should be of type string", "Unexpected message 0") + require.ErrorContains(t, msgs[1].Err, "version '7.2445e+06' is not a valid SemVer", "Unexpected message 1") + assert.ErrorContains(t, msgs[2].Err, "appVersion should be of type string", "Unexpected message 2") }) } diff --git a/internal/chart/v3/lint/rules/deprecations_test.go b/internal/chart/v3/lint/rules/deprecations_test.go index 2b12a88f9..a255abf50 100644 --- a/internal/chart/v3/lint/rules/deprecations_test.go +++ b/internal/chart/v3/lint/rules/deprecations_test.go @@ -16,7 +16,12 @@ limitations under the License. package rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules" -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) func TestValidateNoDeprecations(t *testing.T) { deprecated := &k8sYamlStruct{ @@ -24,18 +29,14 @@ func TestValidateNoDeprecations(t *testing.T) { Kind: "Deployment", } err := validateNoDeprecations(deprecated, nil) - if err == nil { - t.Fatal("Expected deprecated extension to be flagged") - } - depErr := err.(deprecatedAPIError) - if depErr.Message == "" { - t.Fatalf("Expected error message to be non-blank: %v", err) - } + var depErr deprecatedAPIError + require.Error(t, err, "Expected deprecated extension to be flagged") + require.ErrorAs(t, err, &depErr, "Expected error to be of type deprecatedAPIError") + require.NotEmpty(t, depErr.Message, "Expected error message to be non-blank: %v", err) - if err := validateNoDeprecations(&k8sYamlStruct{ + err = validateNoDeprecations(&k8sYamlStruct{ APIVersion: "v1", Kind: "Pod", - }, nil); err != nil { - t.Error("Expected a v1 Pod to not be deprecated") - } + }, nil) + assert.NoError(t, err, "Expected a v1 Pod to not be deprecated") } diff --git a/internal/chart/v3/util/dependencies_test.go b/internal/chart/v3/util/dependencies_test.go index 0a0937e4a..f6ca06da3 100644 --- a/internal/chart/v3/util/dependencies_test.go +++ b/internal/chart/v3/util/dependencies_test.go @@ -21,6 +21,9 @@ import ( "strconv" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/internal/chart/v3" "helm.sh/helm/v4/internal/chart/v3/loader" "helm.sh/helm/v4/pkg/chart/common" @@ -29,9 +32,7 @@ import ( func loadChart(t *testing.T, path string) *chart.Chart { t.Helper() c, err := loader.Load(path) - if err != nil { - t.Fatalf("failed to load testdata: %s", err) - } + require.NoError(t, err, "failed to load testdata") return c } @@ -42,19 +43,11 @@ func TestLoadDependency(t *testing.T) { } check := func(deps []*chart.Dependency) { - if len(deps) != 2 { - t.Errorf("expected 2 dependencies, got %d", len(deps)) - } + require.Len(t, deps, 2, "expected 2 dependencies") for i, tt := range tests { - if deps[i].Name != tt.Name { - t.Errorf("expected dependency named %q, got %q", tt.Name, deps[i].Name) - } - if deps[i].Version != tt.Version { - t.Errorf("expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, deps[i].Version) - } - if deps[i].Repository != tt.Repository { - t.Errorf("expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, deps[i].Repository) - } + assert.Equal(t, tt.Name, deps[i].Name, "expected dependency named %q, got %q", tt.Name, deps[i].Name) + assert.Equal(t, tt.Version, deps[i].Version, "expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, deps[i].Version) + assert.Equal(t, tt.Repository, deps[i].Repository, "expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, deps[i].Repository) } } c := loadChart(t, "testdata/frobnitz") @@ -117,18 +110,12 @@ func TestDependencyEnabled(t *testing.T) { for _, tc := range tests { c := loadChart(t, "testdata/subpop") t.Run(tc.name, func(t *testing.T) { - if err := processDependencyEnabled(c, tc.v, ""); err != nil { - t.Fatalf("error processing enabled dependencies %v", err) - } + require.NoErrorf(t, processDependencyEnabled(c, tc.v, ""), "error processing enabled dependencies") names := extractChartNames(c) - if len(names) != len(tc.e) { - t.Fatalf("slice lengths do not match got %v, expected %v", len(names), len(tc.e)) - } + require.Len(t, names, len(tc.e), "slice lengths do not match got %v, expected %v", len(names), len(tc.e)) for i := range names { - if names[i] != tc.e[i] { - t.Fatalf("slice values do not match got %v, expected %v", names, tc.e) - } + require.Equal(t, tc.e[i], names[i], "slice values do not match got %v, expected %v", names, tc.e) } }) } @@ -219,68 +206,44 @@ func TestProcessDependencyImportValues(t *testing.T) { e["SCBexported2A"] = "blaster" e["global.SC1exported2.all.SC1exported3"] = "SC1expstr" - if err := processDependencyImportValues(c, false); err != nil { - t.Fatalf("processing import values dependencies %v", err) - } + require.NoError(t, processDependencyImportValues(c, false), "processing import values dependencies") cc := common.Values(c.Values) for kk, vv := range e { pv, err := cc.PathValue(kk) - if err != nil { - t.Fatalf("retrieving import values table %v %v", kk, err) - } + require.NoError(t, err, "retrieving import values table %v", kk) switch pv := pv.(type) { case float64: - if s := strconv.FormatFloat(pv, 'f', -1, 64); s != vv { - t.Errorf("failed to match imported float value %v with expected %v for key %q", s, vv, kk) - } + s := strconv.FormatFloat(pv, 'f', -1, 64) + assert.Equalf(t, vv, s, "failed to match imported float value %v with expected %v for key %q", s, vv, kk) case bool: - if b := strconv.FormatBool(pv); b != vv { - t.Errorf("failed to match imported bool value %v with expected %v for key %q", b, vv, kk) - } + b := strconv.FormatBool(pv) + assert.Equalf(t, vv, b, "failed to match imported bool value %v with expected %v for key %q", b, vv, kk) default: - if pv != vv { - t.Errorf("failed to match imported string value %q with expected %q for key %q", pv, vv, kk) - } + assert.Equalf(t, vv, pv, "failed to match imported string value %q with expected %q for key %q", pv, vv, kk) } } // Since this was processed with coalescing there should be no null values. // Here we verify that. _, err := cc.PathValue("ensurenull") - if err == nil { - t.Error("expect nil value not found but found it") - } - switch xerr := err.(type) { - case common.ErrNoValue: - // We found what we expected - default: - t.Errorf("expected an ErrNoValue but got %q instead", xerr) - } + require.Error(t, err, "expect nil value not found but found it") + var xerr common.ErrNoValue + require.ErrorAs(t, err, &xerr, "expected an ErrNoValue") c = loadChart(t, "testdata/subpop") - if err := processDependencyImportValues(c, true); err != nil { - t.Fatalf("processing import values dependencies %v", err) - } + require.NoError(t, processDependencyImportValues(c, true), "processing import values dependencies") cc = common.Values(c.Values) val, err := cc.PathValue("ensurenull") - if err != nil { - t.Error("expect value but ensurenull was not found") - } - if val != nil { - t.Errorf("expect nil value but got %q instead", val) - } + require.NoError(t, err, "expect value but ensurenull was not found") + assert.Nil(t, val, "expect nil value but got %v instead", val) } func TestProcessDependencyImportValuesFromSharedDependencyToAliases(t *testing.T) { c := loadChart(t, "testdata/chart-with-import-from-aliased-dependencies") - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - if err := processDependencyImportValues(c, true); err != nil { - t.Fatalf("processing import values dependencies %v", err) - } + require.NoError(t, processDependencyEnabled(c, c.Values, ""), "expected no errors") + require.NoError(t, processDependencyImportValues(c, true), "processing import values dependencies") e := make(map[string]string) e["foo-defaults.defaultValue"] = "42" @@ -295,12 +258,8 @@ func TestProcessDependencyImportValuesFromSharedDependencyToAliases(t *testing.T cValues := common.Values(c.Values) for kk, vv := range e { pv, err := cValues.PathValue(kk) - if err != nil { - t.Fatalf("retrieving import values table %v %v", kk, err) - } - if pv != vv { - t.Errorf("failed to match imported value %v with expected %v", pv, vv) - } + require.NoError(t, err, "retrieving import values table %v", kk) + assert.Equal(t, vv, pv, "failed to match imported value %v with expected %v", pv, vv) } } @@ -327,25 +286,18 @@ func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) { e["app2.service.port"] = "8080" e["app3.service.port"] = "9090" e["app4.service.port"] = "1234" - if err := processDependencyImportValues(c, true); err != nil { - t.Fatalf("processing import values dependencies %v", err) - } + require.NoError(t, processDependencyImportValues(c, true), "processing import values dependencies") cc := common.Values(c.Values) for kk, vv := range e { pv, err := cc.PathValue(kk) - if err != nil { - t.Fatalf("retrieving import values table %v %v", kk, err) - } + require.NoError(t, err, "retrieving import values table %v", kk) switch pv := pv.(type) { case float64: - if s := strconv.FormatFloat(pv, 'f', -1, 64); s != vv { - t.Errorf("failed to match imported float value %v with expected %v", s, vv) - } + s := strconv.FormatFloat(pv, 'f', -1, 64) + assert.Equalf(t, vv, s, "failed to match imported float value %v with expected %v", s, vv) default: - if pv != vv { - t.Errorf("failed to match imported string value %q with expected %q", pv, vv) - } + assert.Equalf(t, vv, pv, "failed to match imported string value %q with expected %q", pv, vv) } } } @@ -354,127 +306,72 @@ func TestProcessDependencyImportValuesForEnabledCharts(t *testing.T) { c := loadChart(t, "testdata/import-values-from-enabled-subchart/parent-chart") nameOverride := "parent-chart-prod" - if err := processDependencyImportValues(c, true); err != nil { - t.Fatalf("processing import values dependencies %v", err) - } - - if len(c.Dependencies()) != 2 { - t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - - if len(c.Dependencies()) != 1 { - t.Fatal("expected no changes in dependencies") - } - - if len(c.Metadata.Dependencies) != 1 { - t.Fatalf("expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies)) - } + require.NoError(t, processDependencyImportValues(c, true), "processing import values dependencies") + require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) + require.NoError(t, processDependencyEnabled(c, c.Values, ""), "expected no errors") + require.Len(t, c.Dependencies(), 1, "expected no changes in dependencies") + require.Len(t, c.Metadata.Dependencies, 1, "expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies)) prodDependencyValues := c.Dependencies()[0].Values - if prodDependencyValues["nameOverride"] != nameOverride { - t.Fatalf("dependency chart name should be %s but got %s", nameOverride, prodDependencyValues["nameOverride"]) - } + require.Equal(t, nameOverride, prodDependencyValues["nameOverride"], "dependency chart name should be %s but got %s", nameOverride, prodDependencyValues["nameOverride"]) } func TestGetAliasDependency(t *testing.T) { c := loadChart(t, "testdata/frobnitz") req := c.Metadata.Dependencies - if len(req) == 0 { - t.Fatal("there are no dependencies to test") - } + require.NotEmpty(t, req, "there are no dependencies to test") // Success case aliasChart := getAliasDependency(c.Dependencies(), req[0]) - if aliasChart == nil { - t.Fatalf("failed to get dependency chart for alias %s", req[0].Name) - } + require.NotNil(t, aliasChart, "failed to get dependency chart for alias %s", req[0].Name) if req[0].Alias != "" { - if aliasChart.Name() != req[0].Alias { - t.Fatalf("dependency chart name should be %s but got %s", req[0].Alias, aliasChart.Name()) - } - } else if aliasChart.Name() != req[0].Name { - t.Fatalf("dependency chart name should be %s but got %s", req[0].Name, aliasChart.Name()) + require.Equal(t, req[0].Alias, aliasChart.Name(), "dependency chart name should be %s but got %s", req[0].Alias, aliasChart.Name()) + } else { + require.Equal(t, req[0].Name, aliasChart.Name(), "dependency chart name should be %s but got %s", req[0].Name, aliasChart.Name()) } - if req[0].Version != "" { - if !IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version) { - t.Fatal("dependency chart version is not in the compatible range") - } + require.True(t, IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version), "dependency chart version is not in the compatible range") } // Failure case req[0].Name = "something-else" - if aliasChart := getAliasDependency(c.Dependencies(), req[0]); aliasChart != nil { - t.Fatalf("expected no chart but got %s", aliasChart.Name()) - } + require.Nil(t, getAliasDependency(c.Dependencies(), req[0]), "expected no chart") req[0].Version = "something else which is not in the compatible range" - if IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version) { - t.Fatal("dependency chart version outside the compatible range should not be considered compatible") - } + require.False(t, IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version), "dependency chart version outside the compatible range should not be considered compatible") } func TestDependentChartAliases(t *testing.T) { c := loadChart(t, "testdata/dependent-chart-alias") req := c.Metadata.Dependencies - if len(c.Dependencies()) != 2 { - t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - - if len(c.Dependencies()) != 3 { - t.Fatal("expected alias dependencies to be added") - } - - if len(c.Dependencies()) != len(c.Metadata.Dependencies) { - t.Fatalf("expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies())) - } + require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) + require.NoError(t, processDependencyEnabled(c, c.Values, ""), "expected no errors") + require.Len(t, c.Dependencies(), 3, "expected alias dependencies to be added") + require.Len(t, c.Dependencies(), len(c.Metadata.Dependencies), "expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies())) aliasChart := getAliasDependency(c.Dependencies(), req[2]) - if aliasChart == nil { - t.Fatalf("failed to get dependency chart for alias %s", req[2].Name) - } - if aliasChart.Parent() != c { - t.Fatalf("dependency chart has wrong parent, expected %s but got %s", c.Name(), aliasChart.Parent().Name()) - } + require.NotNil(t, aliasChart, "failed to get dependency chart for alias %s", req[2].Name) + require.Equal(t, c, aliasChart.Parent(), "dependency chart has wrong parent, expected %s but got %s", c.Name(), aliasChart.Parent().Name()) if req[2].Alias != "" { - if aliasChart.Name() != req[2].Alias { - t.Fatalf("dependency chart name should be %s but got %s", req[2].Alias, aliasChart.Name()) - } - } else if aliasChart.Name() != req[2].Name { - t.Fatalf("dependency chart name should be %s but got %s", req[2].Name, aliasChart.Name()) + require.Equal(t, req[2].Alias, aliasChart.Name(), "dependency chart name should be %s but got %s", req[2].Alias, aliasChart.Name()) + } else { + require.Equal(t, req[2].Name, aliasChart.Name(), "dependency chart name should be %s but got %s", req[2].Name, aliasChart.Name()) } - req[2].Name = "dummy-name" - if aliasChart := getAliasDependency(c.Dependencies(), req[2]); aliasChart != nil { - t.Fatalf("expected no chart but got %s", aliasChart.Name()) - } + require.Nil(t, getAliasDependency(c.Dependencies(), req[2]), "expected no chart") } func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) { c := loadChart(t, "testdata/dependent-chart-no-requirements-yaml") - if len(c.Dependencies()) != 2 { - t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } + require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - if len(c.Dependencies()) != 2 { - t.Fatal("expected no changes in dependencies") - } + err := processDependencyEnabled(c, c.Values, "") + require.NoError(t, err, "expected no errors") + require.Len(t, c.Dependencies(), 2, "expected no changes in dependencies") } func TestDependentChartWithSubChartsHelmignore(t *testing.T) { @@ -484,86 +381,53 @@ func TestDependentChartWithSubChartsHelmignore(t *testing.T) { func TestDependentChartsWithSubChartsSymlink(t *testing.T) { joonix := filepath.Join("testdata", "joonix") - if err := os.Symlink(filepath.Join("..", "..", "frobnitz"), filepath.Join(joonix, "charts", "frobnitz")); err != nil { - t.Fatal(err) - } + err := os.Symlink(filepath.Join("..", "..", "frobnitz"), filepath.Join(joonix, "charts", "frobnitz")) + require.NoError(t, err, "failed to create symlink") defer os.RemoveAll(filepath.Join(joonix, "charts", "frobnitz")) c := loadChart(t, joonix) - if c.Name() != "joonix" { - t.Fatalf("unexpected chart name: %s", c.Name()) - } - if n := len(c.Dependencies()); n != 1 { - t.Fatalf("expected 1 dependency for this chart, but got %d", n) - } + require.Equal(t, "joonix", c.Name(), "unexpected chart name: %s", c.Name()) + require.Len(t, c.Dependencies(), 1, "expected 1 dependency for this chart") } func TestDependentChartsWithSubchartsAllSpecifiedInDependency(t *testing.T) { c := loadChart(t, "testdata/dependent-chart-with-all-in-requirements-yaml") - if len(c.Dependencies()) != 2 { - t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - } + require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } + err := processDependencyEnabled(c, c.Values, "") + require.NoError(t, err, "expected no errors") - if len(c.Dependencies()) != 2 { - t.Fatal("expected no changes in dependencies") - } + require.Len(t, c.Dependencies(), 2, "expected no changes in dependencies") - if len(c.Dependencies()) != len(c.Metadata.Dependencies) { - t.Fatalf("expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies())) - } + require.Len(t, c.Dependencies(), len(c.Metadata.Dependencies), "expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies())) } func TestDependentChartsWithSomeSubchartsSpecifiedInDependency(t *testing.T) { c := loadChart(t, "testdata/dependent-chart-with-mixed-requirements-yaml") - if len(c.Dependencies()) != 2 { - t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - - if len(c.Dependencies()) != 2 { - t.Fatal("expected no changes in dependencies") - } - - if len(c.Metadata.Dependencies) != 1 { - t.Fatalf("expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies)) - } + require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) + require.NoError(t, processDependencyEnabled(c, c.Values, ""), "expected no errors") + require.Len(t, c.Dependencies(), 2, "expected no changes in dependencies") + require.Len(t, c.Metadata.Dependencies, 1, "expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies)) } func validateDependencyTree(t *testing.T, c *chart.Chart) { t.Helper() for _, dependency := range c.Dependencies() { - if dependency.Parent() != c { - if dependency.Parent() != c { - t.Fatalf("dependency chart %s has wrong parent, expected %s but got %s", dependency.Name(), c.Name(), dependency.Parent().Name()) - } - } - // recurse entire tree - validateDependencyTree(t, dependency) + t.Run(dependency.Name(), func(t *testing.T) { + require.Equal(t, c, dependency.Parent(), "dependency chart %s has wrong parent, expected %s but got %s", dependency.Name(), c.Name(), dependency.Parent().Name()) + // recurse entire tree + validateDependencyTree(t, dependency) + }) } } func TestChartWithDependencyAliasedTwiceAndDoublyReferencedSubDependency(t *testing.T) { c := loadChart(t, "testdata/chart-with-dependency-aliased-twice") - if len(c.Dependencies()) != 1 { - t.Fatalf("expected one dependency for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - - if len(c.Dependencies()) != 2 { - t.Fatal("expected two dependencies after processing aliases") - } + require.Len(t, c.Dependencies(), 1, "expected one dependency for this chart, but got %d", len(c.Dependencies())) + require.NoError(t, processDependencyEnabled(c, c.Values, ""), "expected no errors") + require.Len(t, c.Dependencies(), 2, "expected two dependencies after processing aliases") validateDependencyTree(t, c) } diff --git a/internal/plugin/metadata_test.go b/internal/plugin/metadata_test.go index 6113f30c7..b4994cf68 100644 --- a/internal/plugin/metadata_test.go +++ b/internal/plugin/metadata_test.go @@ -16,10 +16,12 @@ limitations under the License. package plugin import ( + "strconv" "strings" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestValidatePluginData(t *testing.T) { @@ -61,15 +63,15 @@ func TestValidatePluginData(t *testing.T) { {true, mockNoCommand, ""}, // Test no command metadata works {true, mockLegacyCommand, ""}, // Test legacy command metadata works } { - err := item.plug.Metadata().Validate() - if item.pass && err != nil { - t.Errorf("failed to validate case %d: %s", i, err) - } else if !item.pass && err == nil { - t.Errorf("expected case %d to fail", i) - } - if !item.pass && !strings.Contains(err.Error(), item.errString) { - t.Errorf("index [%d]: expected error to contain: %s, but got: %s", i, item.errString, err.Error()) - } + t.Run(strconv.Itoa(i), func(t *testing.T) { + err := item.plug.Metadata().Validate() + if item.pass { + require.NoError(t, err, "failed to validate case %d: %s", i, err) + } else { + require.Error(t, err, "expected case %d to fail", i) + assert.ErrorContains(t, err, item.errString, "expected case %d error to contain %q", i, item.errString) + } + }) } } @@ -122,9 +124,7 @@ func TestMetadataValidateMultipleErrors(t *testing.T) { } err := metadata.Validate() - if err == nil { - t.Fatal("expected validation to fail with multiple errors") - } + require.Error(t, err, "expected validation to fail with multiple errors") errStr := err.Error() @@ -139,9 +139,7 @@ func TestMetadataValidateMultipleErrors(t *testing.T) { } for _, expectedErr := range expectedErrors { - if !strings.Contains(errStr, expectedErr) { - t.Errorf("expected error to contain %q, but got: %v", expectedErr, errStr) - } + assert.Contains(t, errStr, expectedErr, "expected error to contain %q, but got: %v", expectedErr, errStr) } // Verify that the error contains the correct number of error messages @@ -152,7 +150,5 @@ func TestMetadataValidateMultipleErrors(t *testing.T) { } } - if errorCount < len(expectedErrors) { - t.Errorf("expected %d errors, but only found %d in: %v", len(expectedErrors), errorCount, errStr) - } + assert.GreaterOrEqual(t, errorCount, len(expectedErrors), "expected %d errors, but only found %d in: %v", len(expectedErrors), errorCount, errStr) } diff --git a/internal/plugin/verify_test.go b/internal/plugin/verify_test.go index 2828d30b6..5721e0414 100644 --- a/internal/plugin/verify_test.go +++ b/internal/plugin/verify_test.go @@ -20,6 +20,9 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/provenance" ) @@ -41,20 +44,16 @@ func TestVerifyPlugin(t *testing.T) { // Create plugin directory pluginDir := filepath.Join(tempDir, "verify-test-plugin") - if err := os.MkdirAll(pluginDir, 0o755); err != nil { - t.Fatal(err) - } + err := os.MkdirAll(pluginDir, 0o755) + require.NoError(t, err) - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644); err != nil { - t.Fatal(err) - } + err = os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644) + require.NoError(t, err) // Create tarball tarballPath := filepath.Join(tempDir, "verify-test-plugin.tar.gz") tarFile, err := os.Create(tarballPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if err := CreatePluginTarball(pluginDir, "test-plugin", tarFile); err != nil { tarFile.Close() @@ -64,61 +63,41 @@ func TestVerifyPlugin(t *testing.T) { // Sign the plugin with source directory signer, err := provenance.NewFromKeyring(testKeyFile, "helm-test") - if err != nil { - t.Fatal(err) - } - if err := signer.DecryptKey(func(_ string) ([]byte, error) { + require.NoError(t, err) + err = signer.DecryptKey(func(_ string) ([]byte, error) { return []byte(""), nil - }); err != nil { - t.Fatal(err) - } + }) + require.NoError(t, err) // Read the tarball data tarballData, err := os.ReadFile(tarballPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) sig, err := SignPlugin(tarballData, filepath.Base(tarballPath), signer) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Write the signature to .prov file provFile := tarballPath + ".prov" - if err := os.WriteFile(provFile, []byte(sig), 0o644); err != nil { - t.Fatal(err) - } + err = os.WriteFile(provFile, []byte(sig), 0o644) + require.NoError(t, err) // Read the files for verification archiveData, err := os.ReadFile(tarballPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) provData, err := os.ReadFile(provFile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Now verify the plugin verification, err := VerifyPlugin(archiveData, provData, filepath.Base(tarballPath), testPubFile) - if err != nil { - t.Fatalf("Failed to verify plugin: %v", err) - } + require.NoError(t, err, "Failed to verify plugin") // Check verification results - if verification.SignedBy == nil { - t.Error("SignedBy is nil") - } + assert.NotNil(t, verification.SignedBy, "SignedBy is nil") - if verification.FileName != "verify-test-plugin.tar.gz" { - t.Errorf("Expected filename 'verify-test-plugin.tar.gz', got %s", verification.FileName) - } + assert.Equal(t, "verify-test-plugin.tar.gz", verification.FileName, "Expected filename 'verify-test-plugin.tar.gz', got %s", verification.FileName) - if verification.FileHash == "" { - t.Error("FileHash is empty") - } + assert.NotEmpty(t, verification.FileHash, "FileHash is empty") } func TestVerifyPluginBadSignature(t *testing.T) { @@ -126,19 +105,15 @@ func TestVerifyPluginBadSignature(t *testing.T) { // Create a plugin tarball pluginDir := filepath.Join(tempDir, "bad-plugin") - if err := os.MkdirAll(pluginDir, 0o755); err != nil { - t.Fatal(err) - } + err := os.MkdirAll(pluginDir, 0o755) + require.NoError(t, err) - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644); err != nil { - t.Fatal(err) - } + err = os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644) + require.NoError(t, err) tarballPath := filepath.Join(tempDir, "bad-plugin.tar.gz") tarFile, err := os.Create(tarballPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if err := CreatePluginTarball(pluginDir, "test-plugin", tarFile); err != nil { tarFile.Close() @@ -158,26 +133,19 @@ InvalidSignatureData -----END PGP SIGNATURE-----` provFile := tarballPath + ".prov" - if err := os.WriteFile(provFile, []byte(badSig), 0o644); err != nil { - t.Fatal(err) - } + err = os.WriteFile(provFile, []byte(badSig), 0o644) + require.NoError(t, err) // Read the files archiveData, err := os.ReadFile(tarballPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) provData, err := os.ReadFile(provFile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Try to verify - should fail _, err = VerifyPlugin(archiveData, provData, filepath.Base(tarballPath), testPubFile) - if err == nil { - t.Error("Expected verification to fail with bad signature") - } + assert.Error(t, err, "Expected verification to fail with bad signature") } func TestVerifyPluginMissingProvenance(t *testing.T) { @@ -185,21 +153,16 @@ func TestVerifyPluginMissingProvenance(t *testing.T) { tarballPath := filepath.Join(tempDir, "no-prov.tar.gz") // Create a minimal tarball - if err := os.WriteFile(tarballPath, []byte("dummy"), 0o644); err != nil { - t.Fatal(err) - } + err := os.WriteFile(tarballPath, []byte("dummy"), 0o644) + require.NoError(t, err) // Read the tarball data archiveData, err := os.ReadFile(tarballPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Try to verify with empty provenance data _, err = VerifyPlugin(archiveData, nil, filepath.Base(tarballPath), testPubFile) - if err == nil { - t.Error("Expected verification to fail with empty provenance data") - } + assert.Error(t, err, "Expected verification to fail with empty provenance data") } func TestVerifyPluginMalformedData(t *testing.T) { @@ -208,7 +171,5 @@ func TestVerifyPluginMalformedData(t *testing.T) { provData := []byte("fake provenance") _, err := VerifyPlugin(malformedData, provData, "malformed.tar.gz", testPubFile) - if err == nil { - t.Error("Expected malformed data verification to fail, but it succeeded") - } + assert.Error(t, err, "Expected malformed data verification to fail, but it succeeded") } From f280d9c1b5da4e2f25cac76b2fcce58bad35aae9 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 10 Jul 2026 00:24:50 +0200 Subject: [PATCH 432/571] chore(internal): refactor: convert tests to testify assert/require part 2 refactor: convert tests to testify assert/require in internal/chart/v3/util Signed-off-by: Matthieu MOREL --- internal/chart/v3/lint/lint_test.go | 43 +++------- internal/chart/v3/lint/rules/template_test.go | 85 +++++++----------- internal/chart/v3/util/chartfile_test.go | 59 ++++--------- internal/chart/v3/util/create_test.go | 39 +++------ internal/chart/v3/util/expand_test.go | 56 +++--------- internal/chart/v3/util/save_test.go | 86 ++++++------------- internal/plugin/subprocess_commands_test.go | 71 +++++---------- 7 files changed, 129 insertions(+), 310 deletions(-) diff --git a/internal/chart/v3/lint/lint_test.go b/internal/chart/v3/lint/lint_test.go index 6a2558fe0..8916c6e22 100644 --- a/internal/chart/v3/lint/lint_test.go +++ b/internal/chart/v3/lint/lint_test.go @@ -92,35 +92,23 @@ func TestBadChartV3(t *testing.T) { func TestInvalidYaml(t *testing.T) { var values map[string]any m := RunAll(badYamlFileDir, values, namespace).Messages - if len(m) != 1 { - t.Fatalf("All didn't fail with expected errors, got %#v", m) - } - if !strings.Contains(m[0].Err.Error(), "deliberateSyntaxError") { - t.Error("All didn't have the error for deliberateSyntaxError") - } + require.Len(t, m, 1, "All didn't fail with expected errors, got %#v", m) + assert.ErrorContains(t, m[0].Err, "deliberateSyntaxError", "All didn't have the error for deliberateSyntaxError") } func TestInvalidChartYamlV3(t *testing.T) { var values map[string]any m := RunAll(invalidChartFileDir, values, namespace).Messages t.Log(m) - if len(m) != 3 { - t.Fatalf("All didn't fail with expected errors, got %#v", m) - } - if !strings.Contains(m[0].Err.Error(), "failed to strictly parse chart metadata file") { - t.Error("All didn't have the error for duplicate YAML keys") - } + require.Len(t, m, 3, "All didn't fail with expected errors, got %#v", m) + assert.ErrorContains(t, m[0].Err, "failed to strictly parse chart metadata file", "All didn't have the error for duplicate YAML keys") } func TestBadValuesV3(t *testing.T) { var values map[string]any m := RunAll(badValuesFileDir, values, namespace).Messages - if len(m) < 1 { - t.Fatalf("All didn't fail with expected errors, got %#v", m) - } - if !strings.Contains(m[0].Err.Error(), "unable to parse YAML") { - t.Errorf("All didn't have the error for invalid key format: %s", m[0].Err) - } + require.NotEmptyf(t, m, "All didn't fail with expected errors") + assert.ErrorContains(t, m[0].Err, "unable to parse YAML", "All didn't have the error for invalid key format: %s", m[0].Err) } func TestBadCrdFileV3(t *testing.T) { @@ -150,11 +138,7 @@ func TestHelmCreateChart(t *testing.T) { dir := t.TempDir() createdChart, err := chartutil.Create("testhelmcreatepasseslint", dir) - if err != nil { - t.Error(err) - // Fatal is bad because of the defer. - return - } + require.NoError(t, err) // Note: we test with strict=true here, even though others have // strict = false. @@ -178,10 +162,7 @@ func TestHelmCreateChart(t *testing.T) { // of the `--set` flag. func TestHelmCreateChart_CheckDeprecatedWarnings(t *testing.T) { createdChart, err := chartutil.Create("checkdeprecatedwarnings", t.TempDir()) - if err != nil { - t.Error(err) - return - } + require.NoError(t, err) // Add values to enable hpa, and ingress which are disabled by default. // This is the equivalent of: @@ -234,11 +215,7 @@ func TestMalformedTemplate(t *testing.T) { case <-c: t.Fatal("lint malformed template timeout") case <-ch: - if len(m) != 1 { - t.Fatalf("All didn't fail with expected errors, got %#v", m) - } - if !strings.Contains(m[0].Err.Error(), "invalid character '{'") { - t.Error("All didn't have the error for invalid character '{'") - } + require.Len(t, m, 1, "All didn't fail with expected errors, got %#v", m) + assert.ErrorContains(t, m[0].Err, "invalid character '{'", "All didn't have the error for invalid character '{'") } } diff --git a/internal/chart/v3/lint/rules/template_test.go b/internal/chart/v3/lint/rules/template_test.go index 88343b330..0b83e1cd5 100644 --- a/internal/chart/v3/lint/rules/template_test.go +++ b/internal/chart/v3/lint/rules/template_test.go @@ -20,10 +20,12 @@ import ( "fmt" "os" "path/filepath" - "strings" "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/internal/chart/v3" "helm.sh/helm/v4/internal/chart/v3/lint/support" chartutil "helm.sh/helm/v4/internal/chart/v3/util" @@ -36,16 +38,12 @@ func TestValidateAllowedExtension(t *testing.T) { var failTest = []string{"/foo", "/test.toml"} for _, test := range failTest { err := validateAllowedExtension(test) - if err == nil || !strings.Contains(err.Error(), "Valid extensions are .yaml, .yml, .tpl, or .txt") { - t.Errorf("validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test) - } + require.ErrorContains(t, err, "Valid extensions are .yaml, .yml, .tpl, or .txt", "validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test) } var successTest = []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"} for _, test := range successTest { err := validateAllowedExtension(test) - if err != nil { - t.Errorf("validateAllowedExtension('%s') to return no error but got \"%s\"", test, err.Error()) - } + assert.NoError(t, err, "validateAllowedExtension('%s') to return no error", test) } } @@ -59,13 +57,9 @@ func TestTemplateParsing(t *testing.T) { Templates(&linter, values, namespace, strict) res := linter.Messages - if len(res) != 1 { - t.Fatalf("Expected one error, got %d, %v", len(res), res) - } + require.Len(t, res, 1, "Expected one error, got %d, %v", len(res), res) - if !strings.Contains(res[0].Err.Error(), "deliberateSyntaxError") { - t.Errorf("Unexpected error: %s", res[0]) - } + assert.ErrorContains(t, res[0].Err, "deliberateSyntaxError", "Unexpected error: %s", res[0]) } var wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml") @@ -82,9 +76,7 @@ func TestTemplateIntegrationHappyPath(t *testing.T) { Templates(&linter, values, namespace, strict) res := linter.Messages - if len(res) != 0 { - t.Fatalf("Expected no error, got %d, %v", len(res), res) - } + require.Empty(t, res, "Expected no error, got %d, %v", len(res), res) } func TestMultiTemplateFail(t *testing.T) { @@ -92,13 +84,9 @@ func TestMultiTemplateFail(t *testing.T) { Templates(&linter, values, namespace, strict) res := linter.Messages - if len(res) != 1 { - t.Fatalf("Expected 1 error, got %d, %v", len(res), res) - } + require.Len(t, res, 1, "Expected 1 error, got %d, %v", len(res), res) - if !strings.Contains(res[0].Err.Error(), "object name does not conform to Kubernetes naming requirements") { - t.Errorf("Unexpected error: %s", res[0].Err) - } + assert.ErrorContains(t, res[0].Err, "object name does not conform to Kubernetes naming requirements", "Unexpected error: %s", res[0].Err) } func TestValidateMetadataName(t *testing.T) { @@ -176,8 +164,11 @@ func TestValidateMetadataName(t *testing.T) { } for _, tt := range tests { t.Run(fmt.Sprintf("%s/%s", tt.obj.Kind, tt.obj.Metadata.Name), func(t *testing.T) { - if err := validateMetadataName(tt.obj); (err != nil) != tt.wantErr { - t.Errorf("validateMetadataName() error = %v, wantErr %v", err, tt.wantErr) + err := validateMetadataName(tt.obj) + if tt.wantErr { + require.Error(t, err, "validateMetadataName()") + } else { + require.NoError(t, err, "validateMetadataName()") } }) } @@ -207,9 +198,7 @@ func TestDeprecatedAPIFails(t *testing.T) { } tmpdir := t.TempDir() - if err := chartutil.SaveDir(&mychart, tmpdir); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(&mychart, tmpdir)) linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} Templates(&linter, values, namespace, strict) @@ -220,10 +209,9 @@ func TestDeprecatedAPIFails(t *testing.T) { t.Fatalf("Expected 1 lint error, got %d", l) } - err := linter.Messages[0].Err.(deprecatedAPIError) - if err.Deprecated != "apps/v1beta1 Deployment" { - t.Errorf("Surprised to learn that %q is deprecated", err.Deprecated) - } + var err deprecatedAPIError + require.ErrorAs(t, linter.Messages[0].Err, &err, "Expected error to be of type deprecatedAPIError") + assert.Equal(t, "apps/v1beta1 Deployment", err.Deprecated, "Surprised to learn that %q is deprecated", err.Deprecated) } const manifest = `apiVersion: v1 @@ -262,9 +250,7 @@ func TestStrictTemplateParsingMapError(t *testing.T) { }, } dir := t.TempDir() - if err := chartutil.SaveDir(&ch, dir); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(&ch, dir)) linter := &support.Linter{ ChartDir: filepath.Join(dir, ch.Metadata.Name), } @@ -306,9 +292,8 @@ spec: - name: nginx image: nginx:1.14.2 ` - if err := validateMatchSelector(md, manifest); err != nil { - t.Error(err) - } + err := validateMatchSelector(md, manifest) + require.NoError(t, err) manifest = ` apiVersion: apps/v1 kind: Deployment @@ -330,9 +315,8 @@ spec: - name: nginx image: nginx:1.14.2 ` - if err := validateMatchSelector(md, manifest); err != nil { - t.Error(err) - } + err = validateMatchSelector(md, manifest) + require.NoError(t, err) manifest = ` apiVersion: apps/v1 kind: Deployment @@ -351,9 +335,8 @@ spec: - name: nginx image: nginx:1.14.2 ` - if err := validateMatchSelector(md, manifest); err == nil { - t.Error("expected Deployment with no selector to fail") - } + err = validateMatchSelector(md, manifest) + assert.Error(t, err, "expected Deployment with no selector to fail") } func TestValidateTopIndentLevel(t *testing.T) { @@ -392,9 +375,7 @@ func TestEmptyWithCommentsManifests(t *testing.T) { } tmpdir := t.TempDir() - if err := chartutil.SaveDir(&mychart, tmpdir); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(&mychart, tmpdir)) linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} Templates(&linter, values, namespace, strict) @@ -424,9 +405,7 @@ items: helm.sh/resource-policy: keep ` - if err := validateListAnnotations(md, manifest); err == nil { - t.Fatal("expected list with nested keep annotations to fail") - } + require.Error(t, validateListAnnotations(md, manifest), "expected list with nested keep annotations to fail") manifest = ` apiVersion: v1 @@ -439,9 +418,7 @@ items: kind: ConfigMap ` - if err := validateListAnnotations(md, manifest); err != nil { - t.Fatalf("List objects keep annotations should pass. got: %s", err) - } + require.NoError(t, validateListAnnotations(md, manifest), "List objects keep annotations should pass") } func TestIsYamlFileExtension(t *testing.T) { @@ -457,8 +434,6 @@ func TestIsYamlFileExtension(t *testing.T) { for _, test := range tests { result := isYamlFileExtension(test.filename) - if result != test.expected { - t.Errorf("isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected) - } + assert.Equal(t, test.expected, result, "isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected) } } diff --git a/internal/chart/v3/util/chartfile_test.go b/internal/chart/v3/util/chartfile_test.go index 65cb27923..c791930c1 100644 --- a/internal/chart/v3/util/chartfile_test.go +++ b/internal/chart/v3/util/chartfile_test.go @@ -19,6 +19,9 @@ package util import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/internal/chart/v3" ) @@ -35,57 +38,31 @@ func TestLoadChartfile(t *testing.T) { func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { t.Helper() - if f == nil { - t.Fatal("Failed verifyChartfile because f is nil") - } + require.NotNil(t, f, "Failed verifyChartfile because f is nil") - if f.Name != name { - t.Errorf("Expected %s, got %s", name, f.Name) - } + assert.Equal(t, name, f.Name, "Expected %s, got %s", name, f.Name) - if f.Description != "This is a frobnitz." { - t.Errorf("Unexpected description %q", f.Description) - } + assert.Equal(t, "This is a frobnitz.", f.Description, "Unexpected description %q", f.Description) - if f.Version != "1.2.3" { - t.Errorf("Unexpected version %q", f.Version) - } + assert.Equal(t, "1.2.3", f.Version, "Unexpected version %q", f.Version) - if len(f.Maintainers) != 2 { - t.Errorf("Expected 2 maintainers, got %d", len(f.Maintainers)) - } + assert.Len(t, f.Maintainers, 2, "Expected 2 maintainers, got %d", len(f.Maintainers)) - if f.Maintainers[0].Name != "The Helm Team" { - t.Error("Unexpected maintainer name.") - } + assert.Equal(t, "The Helm Team", f.Maintainers[0].Name, "Unexpected maintainer name.") - if f.Maintainers[1].Email != "nobody@example.com" { - t.Error("Unexpected maintainer email.") - } + assert.Equal(t, "nobody@example.com", f.Maintainers[1].Email, "Unexpected maintainer email.") - if len(f.Sources) != 1 { - t.Fatal("Unexpected number of sources") - } + require.Len(t, f.Sources, 1, "Unexpected number of sources") - if f.Sources[0] != "https://example.com/foo/bar" { - t.Errorf("Expected https://example.com/foo/bar, got %s", f.Sources) - } + assert.Equal(t, "https://example.com/foo/bar", f.Sources[0], "Expected https://example.com/foo/bar, got %s", f.Sources) - if f.Home != "http://example.com" { - t.Error("Unexpected home.") - } + assert.Equal(t, "http://example.com", f.Home, "Unexpected home.") - if f.Icon != "https://example.com/64x64.png" { - t.Errorf("Unexpected icon: %q", f.Icon) - } + assert.Equal(t, "https://example.com/64x64.png", f.Icon, "Unexpected icon: %q", f.Icon) - if len(f.Keywords) != 3 { - t.Error("Unexpected keywords") - } + require.Len(t, f.Keywords, 3, "Unexpected keywords") - if len(f.Annotations) != 2 { - t.Fatal("Unexpected annotations") - } + require.Len(t, f.Annotations, 2, "Unexpected annotations") if want, got := "extravalue", f.Annotations["extrakey"]; want != got { t.Errorf("Want %q, but got %q", want, got) @@ -97,9 +74,7 @@ func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { kk := []string{"frobnitz", "sprocket", "dodad"} for i, k := range f.Keywords { - if kk[i] != k { - t.Errorf("Expected %q, got %q", kk[i], k) - } + assert.Equal(t, kk[i], k, "Expected %q, got %q", kk[i], k) } } diff --git a/internal/chart/v3/util/create_test.go b/internal/chart/v3/util/create_test.go index abdd52a82..d4b51eac0 100644 --- a/internal/chart/v3/util/create_test.go +++ b/internal/chart/v3/util/create_test.go @@ -22,6 +22,9 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/internal/chart/v3" "helm.sh/helm/v4/internal/chart/v3/loader" ) @@ -30,20 +33,14 @@ func TestCreate(t *testing.T) { tdir := t.TempDir() c, err := Create("foo", tdir) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) dir := filepath.Join(tdir, "foo") mychart, err := loader.LoadDir(c) - if err != nil { - t.Fatalf("Failed to load newly created chart %q: %s", c, err) - } + require.NoError(t, err, "Failed to load newly created chart %q: %s", c, err) - if mychart.Name() != "foo" { - t.Errorf("Expected name to be 'foo', got %q", mychart.Name()) - } + assert.Equal(t, "foo", mychart.Name(), "Expected name to be 'foo', got %q", mychart.Name()) for _, f := range []string{ ChartfileName, @@ -81,13 +78,9 @@ func TestCreateFrom(t *testing.T) { dir := filepath.Join(tdir, "foo") c := filepath.Join(tdir, cf.Name) mychart, err := loader.LoadDir(c) - if err != nil { - t.Fatalf("Failed to load newly created chart %q: %s", c, err) - } + require.NoError(t, err, "Failed to load newly created chart %q: %s", c, err) - if mychart.Name() != "foo" { - t.Errorf("Expected name to be 'foo', got %q", mychart.Name()) - } + assert.Equal(t, "foo", mychart.Name(), "Expected name to be 'foo', got %q", mychart.Name()) for _, f := range []string{ ChartfileName, @@ -100,9 +93,7 @@ func TestCreateFrom(t *testing.T) { // Check each file to make sure has been replaced b, err := os.ReadFile(filepath.Join(dir, f)) - if err != nil { - t.Errorf("Unable to read file %s: %s", f, err) - } + require.NoError(t, err, "Unable to read file %s: %s", f, err) if bytes.Contains(b, []byte("")) { t.Errorf("File %s contains ", f) } @@ -131,17 +122,11 @@ func TestCreate_Overwrite(t *testing.T) { } data, err := os.ReadFile(tplname) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if string(data) == "FOO" { - t.Fatal("File that should have been modified was not.") - } + require.NotEqual(t, "FOO", string(data), "File that should have been modified was not.") - if errlog.Len() == 0 { - t.Error("Expected warnings about overwriting files.") - } + assert.NotEqual(t, 0, errlog.Len(), "Expected warnings about overwriting files.") } func TestValidateChartName(t *testing.T) { diff --git a/internal/chart/v3/util/expand_test.go b/internal/chart/v3/util/expand_test.go index e9e298b81..a7a756485 100644 --- a/internal/chart/v3/util/expand_test.go +++ b/internal/chart/v3/util/expand_test.go @@ -72,9 +72,7 @@ func makeTestChartArchive(t *testing.T, chartName, sourceDir string) *bytes.Buff return nil }) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) err = tw.Close() require.NoError(t, err) @@ -88,9 +86,7 @@ func TestExpand(t *testing.T) { dest := t.TempDir() reader, err := os.Open("testdata/frobnitz-1.2.3.tgz") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if err := Expand(dest, reader); err != nil { t.Fatal(err) @@ -98,41 +94,29 @@ func TestExpand(t *testing.T) { expectedChartPath := filepath.Join(dest, "frobnitz") fi, err := os.Stat(expectedChartPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if !fi.IsDir() { t.Fatalf("expected a chart directory at %s", expectedChartPath) } dir, err := os.Open(expectedChartPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) fis, err := dir.Readdir(0) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) expectLen := 11 - if len(fis) != expectLen { - t.Errorf("Expected %d files, but got %d", expectLen, len(fis)) - } + assert.Len(t, fis, expectLen, "Expected %d files, but got %d", expectLen, len(fis)) for _, fi := range fis { expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name())) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // os.Stat can return different values for directories, based on the OS // for Linux, for example, os.Stat always returns the size of the directory // (value-4096) regardless of the size of the contents of the directory mode := expect.Mode() if !mode.IsDir() { - if fi.Size() != expect.Size() { - t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) - } + assert.Equal(t, expect.Size(), fi.Size(), "Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) } } } @@ -168,41 +152,29 @@ func TestExpandFile(t *testing.T) { expectedChartPath := filepath.Join(dest, "frobnitz") fi, err := os.Stat(expectedChartPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if !fi.IsDir() { t.Fatalf("expected a chart directory at %s", expectedChartPath) } dir, err := os.Open(expectedChartPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) fis, err := dir.Readdir(0) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) expectLen := 11 - if len(fis) != expectLen { - t.Errorf("Expected %d files, but got %d", expectLen, len(fis)) - } + assert.Len(t, fis, expectLen, "Expected %d files, but got %d", expectLen, len(fis)) for _, fi := range fis { expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name())) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // os.Stat can return different values for directories, based on the OS // for Linux, for example, os.Stat always returns the size of the directory // (value-4096) regardless of the size of the contents of the directory mode := expect.Mode() if !mode.IsDir() { - if fi.Size() != expect.Size() { - t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) - } + assert.Equal(t, expect.Size(), fi.Size(), "Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) } } } diff --git a/internal/chart/v3/util/save_test.go b/internal/chart/v3/util/save_test.go index cbe9b853f..e729ad3b3 100644 --- a/internal/chart/v3/util/save_test.go +++ b/internal/chart/v3/util/save_test.go @@ -32,6 +32,9 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/internal/chart/v3" "helm.sh/helm/v4/internal/chart/v3/loader" "helm.sh/helm/v4/pkg/chart/common" @@ -59,9 +62,7 @@ func TestSave(t *testing.T) { chartWithInvalidJSON := withSchema(*c, []byte("{")) where, err := Save(c, dest) - if err != nil { - t.Fatalf("Failed to save: %s", err) - } + require.NoError(t, err, "Failed to save: %s", err) if !strings.HasPrefix(where, dest) { t.Fatalf("Expected %q to start with %q", where, dest) } @@ -70,12 +71,8 @@ func TestSave(t *testing.T) { } c2, err := loader.LoadFile(where) - if err != nil { - t.Fatal(err) - } - if c2.Name() != c.Name() { - t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name()) - } + require.NoError(t, err) + require.Equal(t, c.Name(), c2.Name(), "Expected chart archive to have %q, got %q", c.Name(), c2.Name()) if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" { t.Fatal("Files data did not match") } @@ -92,19 +89,11 @@ func TestSave(t *testing.T) { c.Metadata.APIVersion = chart.APIVersionV3 where, err = Save(c, dest) - if err != nil { - t.Fatalf("Failed to save: %s", err) - } + require.NoError(t, err, "Failed to save: %s", err) c2, err = loader.LoadFile(where) - if err != nil { - t.Fatal(err) - } - if c2.Lock == nil { - t.Fatal("Expected v3 chart archive to contain a Chart.lock file") - } - if c2.Lock.Digest != c.Lock.Digest { - t.Fatal("Chart.lock data did not match") - } + require.NoError(t, err) + require.NotNil(t, c2.Lock, "Expected v3 chart archive to contain a Chart.lock file") + require.Equal(t, c.Lock.Digest, c2.Lock.Digest, "Chart.lock data did not match") }) } @@ -122,9 +111,7 @@ func TestSave(t *testing.T) { }, } _, err := Save(c, tmp) - if err == nil { - t.Fatal("Expected error saving chart with invalid name") - } + require.Error(t, err, "Expected error saving chart with invalid name") } // Creates a copy with a different schema; does not modify anything. @@ -165,14 +152,10 @@ func TestSavePreservesTimestamps(t *testing.T) { } where, err := Save(c, tmp) - if err != nil { - t.Fatalf("Failed to save: %s", err) - } + require.NoError(t, err, "Failed to save: %s", err) allHeaders, err := retrieveAllHeadersFromTar(where) - if err != nil { - t.Fatalf("Failed to parse tar: %v", err) - } + require.NoError(t, err, "Failed to parse tar: %v", err) roundedTime := initialCreateTime.Round(time.Second) for _, header := range allHeaders { @@ -234,37 +217,26 @@ func TestSaveDir(t *testing.T) { }, } - if err := SaveDir(c, tmp); err != nil { - t.Fatalf("Failed to save: %s", err) - } + require.NoError(t, SaveDir(c, tmp), "Failed to save") c2, err := loader.LoadDir(tmp + "/ahab") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if c2.Name() != c.Name() { - t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name()) - } + require.Equal(t, c.Name(), c2.Name(), "Expected chart archive to have %q, got %q", c.Name(), c2.Name()) - if len(c2.Templates) != 1 || c2.Templates[0].Name != c.Templates[0].Name { - t.Fatal("Templates data did not match") - } + require.Len(t, c2.Templates, 1) + require.Equal(t, c.Templates[0].Name, c2.Templates[0].Name, "Templates data did not match") - if len(c2.Files) != 1 || c2.Files[0].Name != c.Files[0].Name { - t.Fatal("Files data did not match") - } + require.Len(t, c2.Files, 1) + require.Equal(t, c.Files[0].Name, c2.Files[0].Name, "Files data did not match") tmp2 := t.TempDir() c.Metadata.Name = "../ahab" pth := filepath.Join(tmp2, "tmpcharts") - if err := os.MkdirAll(filepath.Join(pth), 0o755); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(filepath.Join(pth), 0o755), "Failed to create directory") - if err := SaveDir(c, pth); err.Error() != "\"../ahab\" is not a valid chart name" { - t.Fatalf("Did not get expected error for chart named %q", c.Name()) - } + err = SaveDir(c, pth) + assert.EqualError(t, err, "\"../ahab\" is not a valid chart name", "Did not get expected error for chart named %q", c.Name()) } func TestRepeatableSave(t *testing.T) { @@ -325,18 +297,12 @@ func TestRepeatableSave(t *testing.T) { // create package dest := path.Join(tmp, "newdir") where, err := Save(test.chart, dest) - if err != nil { - t.Fatalf("Failed to save: %s", err) - } + require.NoError(t, err, "Failed to save: %s", err) // get shasum for package result, err := sha256Sum(where) - if err != nil { - t.Fatalf("Failed to check shasum: %s", err) - } + require.NoError(t, err, "Failed to check shasum: %s", err) // assert that the package SHA is what we wanted. - if result != test.want { - t.Errorf("FormatName() result = %v, want %v", result, test.want) - } + assert.Equal(t, test.want, result, "FormatName() result = %v, want %v", result, test.want) }) } } diff --git a/internal/plugin/subprocess_commands_test.go b/internal/plugin/subprocess_commands_test.go index dbce42f6a..6aa0a7435 100644 --- a/internal/plugin/subprocess_commands_test.go +++ b/internal/plugin/subprocess_commands_test.go @@ -21,6 +21,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestPrepareCommand(t *testing.T) { @@ -36,12 +37,8 @@ func TestPrepareCommand(t *testing.T) { env := map[string]string{} cmd, args, err := PrepareCommands(platformCommand, true, []string{}, env) - if err != nil { - t.Fatal(err) - } - if cmd != cmdMain { - t.Fatalf("Expected %q, got %q", cmdMain, cmd) - } + require.NoError(t, err) + require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) if !reflect.DeepEqual(args, cmdArgs) { t.Fatalf("Expected %v, got %v", cmdArgs, args) } @@ -94,9 +91,7 @@ func TestPrepareCommandExtraArgs(t *testing.T) { env := map[string]string{} cmd, args, err := PrepareCommands(platformCommand, true, testExtraArgs, env) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) assert.Equal(t, tc.expected.cmdMain, cmd, "Expected command to match") assert.Equal(t, tc.expected.args, args, "Expected args to match") }) @@ -116,12 +111,8 @@ func TestPrepareCommands(t *testing.T) { env := map[string]string{} cmd, args, err := PrepareCommands(cmds, true, []string{}, env) - if err != nil { - t.Fatal(err) - } - if cmd != cmdMain { - t.Fatalf("Expected %q, got %q", cmdMain, cmd) - } + require.NoError(t, err) + require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) if !reflect.DeepEqual(args, cmdArgs) { t.Fatalf("Expected %v, got %v", cmdArgs, args) } @@ -143,12 +134,8 @@ func TestPrepareCommandsExtraArgs(t *testing.T) { env := map[string]string{} cmd, args, err := PrepareCommands(cmds, true, extraArgs, env) - if err != nil { - t.Fatal(err) - } - if cmd != cmdMain { - t.Fatalf("Expected %q, got %q", cmdMain, cmd) - } + require.NoError(t, err) + require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) if !reflect.DeepEqual(args, expectedArgs) { t.Fatalf("Expected %v, got %v", expectedArgs, args) } @@ -166,12 +153,8 @@ func TestPrepareCommandsNoArch(t *testing.T) { env := map[string]string{} cmd, args, err := PrepareCommands(cmds, true, []string{}, env) - if err != nil { - t.Fatal(err) - } - if cmd != cmdMain { - t.Fatalf("Expected %q, got %q", cmdMain, cmd) - } + require.NoError(t, err) + require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) if !reflect.DeepEqual(args, cmdArgs) { t.Fatalf("Expected %v, got %v", cmdArgs, args) } @@ -189,12 +172,8 @@ func TestPrepareCommandsNoOsNoArch(t *testing.T) { env := map[string]string{} cmd, args, err := PrepareCommands(cmds, true, []string{}, env) - if err != nil { - t.Fatal(err) - } - if cmd != cmdMain { - t.Fatalf("Expected %q, got %q", cmdMain, cmd) - } + require.NoError(t, err) + require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) if !reflect.DeepEqual(args, cmdArgs) { t.Fatalf("Expected %v, got %v", cmdArgs, args) } @@ -208,18 +187,16 @@ func TestPrepareCommandsNoMatch(t *testing.T) { } env := map[string]string{} - if _, _, err := PrepareCommands(cmds, true, []string{}, env); err == nil { - t.Fatal("Expected error to be returned") - } + _, _, err := PrepareCommands(cmds, true, []string{}, env) + require.Error(t, err, "Expected error to be returned") } func TestPrepareCommandsNoCommands(t *testing.T) { cmds := []PlatformCommand{} env := map[string]string{} - if _, _, err := PrepareCommands(cmds, true, []string{}, env); err == nil { - t.Fatal("Expected error to be returned") - } + _, _, err := PrepareCommands(cmds, true, []string{}, env) + require.Error(t, err, "Expected error to be returned") } func TestPrepareCommandsExpand(t *testing.T) { @@ -237,12 +214,8 @@ func TestPrepareCommandsExpand(t *testing.T) { } cmd, args, err := PrepareCommands(cmds, true, []string{}, env) - if err != nil { - t.Fatal(err) - } - if cmd != cmdMain { - t.Fatalf("Expected %q, got %q", cmdMain, cmd) - } + require.NoError(t, err) + require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) if !reflect.DeepEqual(args, expectedArgs) { t.Fatalf("Expected %v, got %v", expectedArgs, args) } @@ -260,12 +233,8 @@ func TestPrepareCommandsNoExpand(t *testing.T) { } cmd, args, err := PrepareCommands(cmds, false, []string{}, env) - if err != nil { - t.Fatal(err) - } - if cmd != cmdMain { - t.Fatalf("Expected %q, got %q", cmdMain, cmd) - } + require.NoError(t, err) + require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) if !reflect.DeepEqual(args, cmdArgs) { t.Fatalf("Expected %v, got %v", cmdArgs, args) } From a02a5713deef0c0c1bf4359dcb0218a9c640224d Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Tue, 14 Jul 2026 23:50:53 +0200 Subject: [PATCH 433/571] chore: fix elseif and ifElseChain issues from gocritic (#32289) * chore: fix elseif and ifElseChain issues from gocritic Signed-off-by: Matthieu MOREL * test: enhance assertions in OCIInstaller and list tests using testify Signed-off-by: Matthieu MOREL --------- Signed-off-by: Matthieu MOREL --- .golangci.yml | 2 -- internal/chart/v3/lint/rules/chartfile.go | 10 +++--- internal/plugin/installer/installer.go | 9 ++--- .../plugin/installer/oci_installer_test.go | 17 ++++----- pkg/chart/common/util/coalesce.go | 10 +++--- pkg/chart/v2/lint/rules/chartfile.go | 10 +++--- pkg/cmd/list_test.go | 12 +++---- pkg/cmd/registry_login.go | 7 ++-- pkg/kube/client_test.go | 13 ++----- pkg/provenance/sign_test.go | 35 ++++++------------- pkg/pusher/ocipusher_test.go | 14 ++++---- pkg/registry/client.go | 6 ++-- pkg/registry/registry_test.go | 9 ++--- pkg/registry/transport.go | 7 ++-- pkg/storage/driver/memory_test.go | 31 +++++++--------- 15 files changed, 78 insertions(+), 114 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index b9f43d434..6727d3001 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -87,10 +87,8 @@ linters: - badCall - commentedOutCode - deferInLoop - - elseif - exposedSyncMutex - hugeParam - - ifElseChain - importShadow - paramTypeCombine - ptrToRefParam diff --git a/internal/chart/v3/lint/rules/chartfile.go b/internal/chart/v3/lint/rules/chartfile.go index 6f51a1de5..db7ac61dc 100644 --- a/internal/chart/v3/lint/rules/chartfile.go +++ b/internal/chart/v3/lint/rules/chartfile.go @@ -160,14 +160,14 @@ func validateChartVersion(cf *chart.Metadata) error { func validateChartMaintainer(cf *chart.Metadata) error { for _, maintainer := range cf.Maintainers { - if maintainer == nil { + switch { + case maintainer == nil: return errors.New("a maintainer entry is empty") - } - if maintainer.Name == "" { + case maintainer.Name == "": return errors.New("each maintainer requires a name") - } else if maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email) { + case maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email): return fmt.Errorf("invalid email '%s' for maintainer '%s'", maintainer.Email, maintainer.Name) - } else if maintainer.URL != "" && !govalidator.IsURL(maintainer.URL) { + case maintainer.URL != "" && !govalidator.IsURL(maintainer.URL): return fmt.Errorf("invalid url '%s' for maintainer '%s'", maintainer.URL, maintainer.Name) } } diff --git a/internal/plugin/installer/installer.go b/internal/plugin/installer/installer.go index 865570773..4728879f5 100644 --- a/internal/plugin/installer/installer.go +++ b/internal/plugin/installer/installer.go @@ -136,15 +136,16 @@ func Update(i Installer) error { // NewForSource determines the correct Installer for the given source. func NewForSource(source, version string) (installer Installer, err error) { - if strings.HasPrefix(source, registry.OCIScheme+"://") { + switch { + case strings.HasPrefix(source, registry.OCIScheme+"://"): // Source is an OCI registry reference installer, err = NewOCIInstaller(source) - } else if isLocalReference(source) { + case isLocalReference(source): // Source is a local directory installer, err = NewLocalInstaller(source) - } else if isRemoteHTTPArchive(source) { + case isRemoteHTTPArchive(source): installer, err = NewHTTPInstaller(source) - } else { + default: installer, err = NewVCSInstaller(source, version) } diff --git a/internal/plugin/installer/oci_installer_test.go b/internal/plugin/installer/oci_installer_test.go index 650e46add..c3bebdb52 100644 --- a/internal/plugin/installer/oci_installer_test.go +++ b/internal/plugin/installer/oci_installer_test.go @@ -33,6 +33,8 @@ import ( "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/internal/test/ensure" "helm.sh/helm/v4/pkg/cli" @@ -415,18 +417,11 @@ func TestOCIInstaller_Install_WithGetterOptions(t *testing.T) { // Install the plugin err = Install(installer) if tc.wantErr { - if err == nil { - t.Error("Expected installation to fail, but it succeeded") - } + require.Error(t, err, "Expected installation to fail, but it succeeded") } else { - if err != nil { - t.Errorf("Expected installation to succeed, got error: %v", err) - } else { - // Verify plugin was installed to the actual path - if !isPlugin(actualPath) { - t.Errorf("Expected plugin directory %s to contain plugin.yaml", actualPath) - } - } + require.NoError(t, err, "Expected installation to succeed, got error: %v", err) + // Verify plugin was installed to the actual path + assert.True(t, isPlugin(actualPath), "Expected plugin directory %s to contain plugin.yaml", actualPath) } }) } diff --git a/pkg/chart/common/util/coalesce.go b/pkg/chart/common/util/coalesce.go index 999eeb208..c1f73b16d 100644 --- a/pkg/chart/common/util/coalesce.go +++ b/pkg/chart/common/util/coalesce.go @@ -336,20 +336,22 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]any, prefix strin // values. for key, val := range src { fullkey := concatPrefix(prefix, key) - if dv, ok := dst[key]; ok && !merge && dv == nil && srcOriginalNonNil[key] { + dv, ok := dst[key] + switch { + case ok && !merge && dv == nil && srcOriginalNonNil[key]: // When coalescing (not merging), if dst has nil and src has a non-nil // value, the user is nullifying a chart default - remove the key. // But if src also has nil (or key not in src), preserve the nil delete(dst, key) - } else if !ok { + case !ok: dst[key] = val - } else if istable(val) { + case istable(val): if istable(dv) { coalesceTablesFullKey(printf, dv.(map[string]any), val.(map[string]any), fullkey, merge) } else { printf("warning: cannot overwrite table with non table for %s (%v)", fullkey, val) } - } else if istable(dv) && val != nil { + case istable(dv) && val != nil: printf("warning: destination for %s is a table. Ignoring non-table value (%v)", fullkey, val) } } diff --git a/pkg/chart/v2/lint/rules/chartfile.go b/pkg/chart/v2/lint/rules/chartfile.go index cb58bbac0..08508f46e 100644 --- a/pkg/chart/v2/lint/rules/chartfile.go +++ b/pkg/chart/v2/lint/rules/chartfile.go @@ -171,14 +171,14 @@ func validateChartVersionStrictSemVerV2(cf *chart.Metadata) error { func validateChartMaintainer(cf *chart.Metadata) error { for _, maintainer := range cf.Maintainers { - if maintainer == nil { + switch { + case maintainer == nil: return errors.New("a maintainer entry is empty") - } - if maintainer.Name == "" { + case maintainer.Name == "": return errors.New("each maintainer requires a name") - } else if maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email) { + case maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email): return fmt.Errorf("invalid email '%s' for maintainer '%s'", maintainer.Email, maintainer.Name) - } else if maintainer.URL != "" && !govalidator.IsURL(maintainer.URL) { + case maintainer.URL != "" && !govalidator.IsURL(maintainer.URL): return fmt.Errorf("invalid url '%s' for maintainer '%s'", maintainer.URL, maintainer.Name) } } diff --git a/pkg/cmd/list_test.go b/pkg/cmd/list_test.go index 35153465a..f45616250 100644 --- a/pkg/cmd/list_test.go +++ b/pkg/cmd/list_test.go @@ -20,6 +20,9 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/release/common" release "helm.sh/helm/v4/pkg/release/v1" @@ -348,13 +351,8 @@ func TestReleaseListWriter(t *testing.T) { t.Run(tt.name, func(t *testing.T) { writer := newReleaseListWriter(tt.releases, tt.timeFormat, tt.noHeaders, tt.noColor) - if writer == nil { - t.Error("Expected writer to be non-nil") - } else { - if len(writer.releases) != len(tt.releases) { - t.Errorf("Expected %d releases, got %d", len(tt.releases), len(writer.releases)) - } - } + require.NotNil(t, writer, "Expected writer to be non-nil") + assert.Len(t, writer.releases, len(tt.releases), "Expected %d releases, got %d", len(tt.releases), len(writer.releases)) }) } } diff --git a/pkg/cmd/registry_login.go b/pkg/cmd/registry_login.go index 1350fb244..bffc56445 100644 --- a/pkg/cmd/registry_login.go +++ b/pkg/cmd/registry_login.go @@ -96,14 +96,15 @@ func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStd username := usernameOpt password := passwordOpt - if passwordFromStdinOpt { + switch { + case passwordFromStdinOpt: passwordFromStdin, err := io.ReadAll(os.Stdin) if err != nil { return "", "", err } password = strings.TrimSuffix(string(passwordFromStdin), "\n") password = strings.TrimSuffix(password, "\r") - } else if password == "" { + case password == "": if username == "" { username, err = readLine("Username: ", false) if err != nil { @@ -126,7 +127,7 @@ func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStd return "", "", errors.New("password required") } } - } else { + default: slog.Warn("using --password via the CLI is insecure. Use --password-stdin") } diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 4a28fc5ed..b5f1f5f12 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -1400,18 +1400,9 @@ func TestIsReachable(t *testing.T) { err := client.IsReachable() if tt.expectError { - if err == nil { - t.Error("expected error but got nil") - return - } - - if !strings.Contains(err.Error(), tt.errorContains) { - t.Errorf("expected error message to contain '%s', got: %v", tt.errorContains, err) - } + require.ErrorContains(t, err, tt.errorContains) } else { - if err != nil { - t.Errorf("expected no error but got: %v", err) - } + require.NoError(t, err) } }) } diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go index faf402b43..3f8e654f9 100644 --- a/pkg/provenance/sign_test.go +++ b/pkg/provenance/sign_test.go @@ -362,41 +362,28 @@ func TestClearSignError(t *testing.T) { func TestVerify(t *testing.T) { signer, err := NewFromFiles(testKeyfile, testPubfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Read the chart file data archiveData, err := os.ReadFile(testChartfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Read the signature file data sigData, err := os.ReadFile(testSigBlock) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if ver, err := signer.Verify(archiveData, sigData, filepath.Base(testChartfile)); err != nil { - t.Errorf("Failed to pass verify. Err: %s", err) - } else if ver.FileHash == "" { - t.Error("Verification is missing hash.") - } else if ver.SignedBy == nil { - t.Error("No SignedBy field") - } else if ver.FileName != filepath.Base(testChartfile) { - t.Errorf("FileName is unexpectedly %q", ver.FileName) - } + ver, err := signer.Verify(archiveData, sigData, filepath.Base(testChartfile)) + require.NoError(t, err, "Failed to pass verify") + assert.NotEmpty(t, ver.FileHash, "Verification is missing hash.") + assert.NotNil(t, ver.SignedBy, "No SignedBy field") + assert.Equalf(t, filepath.Base(testChartfile), ver.FileName, "FileName is unexpectedly %q", ver.FileName) // Read the tampered signature file data tamperedSigData, err := os.ReadFile(testTamperedSigBlock) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if _, err = signer.Verify(archiveData, tamperedSigData, filepath.Base(testChartfile)); err == nil { - t.Errorf("Expected %s to fail.", testTamperedSigBlock) - } + _, err = signer.Verify(archiveData, tamperedSigData, filepath.Base(testChartfile)) + require.Errorf(t, err, "Expected %s to fail.", testTamperedSigBlock) switch err.(type) { case pgperrors.SignatureError: diff --git a/pkg/pusher/ocipusher_test.go b/pkg/pusher/ocipusher_test.go index 52536dfd2..a4542500a 100644 --- a/pkg/pusher/ocipusher_test.go +++ b/pkg/pusher/ocipusher_test.go @@ -24,6 +24,8 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/registry" ) @@ -378,16 +380,12 @@ func TestOCIPusher_Push_ChartOperations(t *testing.T) { err = pusher.Push(chartRef, tt.href) if tt.expectError { - if err == nil { - t.Fatal("Expected error but got none") - } - if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) { - t.Errorf("Expected error containing %q, got %q", tt.errorContains, err.Error()) + require.Error(t, err) + if tt.errorContains != "" { + require.ErrorContains(t, err, tt.errorContains) } } else { - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } + require.NoError(t, err) } }) } diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 7a1c8f449..cba346dea 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -852,10 +852,8 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (string, *ur if version == "" { // Use OCI URI tag as default version = registryReference.Tag - } else { - if registryReference.Tag != "" && registryReference.Tag != version { - return "", nil, fmt.Errorf("chart reference and version mismatch: %s is not %s", version, registryReference.Tag) - } + } else if registryReference.Tag != "" && registryReference.Tag != version { + return "", nil, fmt.Errorf("chart reference and version mismatch: %s is not %s", version, registryReference.Tag) } if registryReference.Digest != "" { diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index 44af191b1..429b15113 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -200,7 +200,8 @@ func teardown(suite *TestRegistry) { func initCompromisedRegistryTestServer() string { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.Contains(r.URL.Path, "manifests") { + switch { + case strings.Contains(r.URL.Path, "manifests"): w.Header().Set("Content-Type", "application/vnd.oci.image.manifest.v1+json") w.WriteHeader(http.StatusOK) @@ -217,17 +218,17 @@ func initCompromisedRegistryTestServer() string { } ] }`, ConfigMediaType, ChartLayerMediaType) - } else if r.URL.Path == "/v2/testrepo/supposedlysafechart/blobs/sha256:a705ee2789ab50a5ba20930f246dbd5cc01ff9712825bb98f57ee8414377f133" { + case r.URL.Path == "/v2/testrepo/supposedlysafechart/blobs/sha256:a705ee2789ab50a5ba20930f246dbd5cc01ff9712825bb98f57ee8414377f133": w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("{\"name\":\"mychart\",\"version\":\"0.1.0\",\"description\":\"A Helm chart for Kubernetes\\n" + "an 'application' or a 'library' chart.\",\"apiVersion\":\"v2\",\"appVersion\":\"1.16.0\",\"type\":" + "\"application\"}")) - } else if r.URL.Path == "/v2/testrepo/supposedlysafechart/blobs/sha256:ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb" { + case r.URL.Path == "/v2/testrepo/supposedlysafechart/blobs/sha256:ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb": w.Header().Set("Content-Type", ChartLayerMediaType) w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("b")) - } else { + default: w.WriteHeader(http.StatusInternalServerError) } })) diff --git a/pkg/registry/transport.go b/pkg/registry/transport.go index 68e4ac587..beec43cdd 100644 --- a/pkg/registry/transport.go +++ b/pkg/registry/transport.go @@ -71,11 +71,12 @@ func (t *LoggingTransport) RoundTrip(req *http.Request) (resp *http.Response, er slog.Debug(req.Method, "id", id, "url", req.URL, "header", logHeader(req.Header)) resp, err = t.RoundTripper.RoundTrip(req) - if err != nil { + switch { + case err != nil: slog.Debug("Response"[:len(req.Method)], "id", id, "error", err) - } else if resp != nil { + case resp != nil: slog.Debug("Response"[:len(req.Method)], "id", id, "status", resp.Status, "header", logHeader(resp.Header), "body", logResponseBody(resp)) - } else { + default: slog.Debug("Response"[:len(req.Method)], "id", id, "response", "nil") } diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go index f821e30d4..6040daf59 100644 --- a/pkg/storage/driver/memory_test.go +++ b/pkg/storage/driver/memory_test.go @@ -261,27 +261,20 @@ func TestMemoryDelete(t *testing.T) { } startLen := len(start) for _, tt := range tests { - ts.SetNamespace(tt.namespace) + t.Run(tt.desc, func(t *testing.T) { + ts.SetNamespace(tt.namespace) - rel, err := ts.Delete(tt.key) - var rls *rspb.Release - if err == nil { - rls = convertReleaserToV1(t, rel) - } - if err != nil { - if !tt.err { - t.Fatalf("Failed %q to get '%s': %q\n", tt.desc, tt.key, err) + rel, err := ts.Delete(tt.key) + if tt.err { + require.Errorf(t, err, "Did not get expected error for %q '%s'\n", tt.desc, tt.key) + } else { + require.NoErrorf(t, err, "Failed %q to get '%s'", tt.desc, tt.key) + rls := convertReleaserToV1(t, rel) + require.Equalf(t, tt.key, fmt.Sprintf("%s.v%d", rls.Name, rls.Version), "Asked for delete on %s, but deleted %d", tt.key, rls.Version) } - continue - } else if tt.err { - t.Fatalf("Did not get expected error for %q '%s'\n", tt.desc, tt.key) - } else if fmt.Sprintf("%s.v%d", rls.Name, rls.Version) != tt.key { - t.Fatalf("Asked for delete on %s, but deleted %d", tt.key, rls.Version) - } - _, err = ts.Get(tt.key) - if err == nil { - t.Error("Expected an error when asking for a deleted key") - } + _, err = ts.Get(tt.key) + require.Error(t, err, "Expected an error when asking for a deleted key") + }) } // Make sure that the deleted records are gone. From aa1ae3a36017156817e89e59eee0e88af9fc6365 Mon Sep 17 00:00:00 2001 From: Jakub Jaruszewski Date: Wed, 15 Jul 2026 00:37:53 +0200 Subject: [PATCH 434/571] Fix missing conflict retry with server-side apply (#32088) * Fix missing conflict retry with server-side apply Signed-off-by: Jakub Jaruszewski * Only retry on resourcequota transient conflict Signed-off-by: Jakub Jaruszewski * Use isServerSideRetryable + naming nitpick Signed-off-by: Jakub Jaruszewski --------- Signed-off-by: Jakub Jaruszewski --- pkg/kube/client.go | 46 ++++++++++++++++++++++++++++++++++------- pkg/kube/client_test.go | 25 +++++++++++++++++++++- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 4e6d5c3a9..e363fad61 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -321,20 +321,24 @@ func (c *Client) makeCreateApplyFunc(serverSideApply, forceConflicts, dryRun boo slog.String("fieldValidationDirective", string(fieldValidationDirective))) return func(target *resource.Info) error { - err := patchResourceServerSide(target, dryRun, forceConflicts, fieldValidationDirective) - logger := c.Logger().With( slog.String("namespace", target.Namespace), slog.String("name", target.Name), slog.String("gvk", target.Mapping.GroupVersionKind.String())) - if err != nil { - logger.Debug("Error creating resource via patch", slog.Any("error", err)) - return err - } - logger.Debug("Created resource via patch") + return retry.OnError( + retry.DefaultRetry, + isServerSideRetryable, + func() error { + err := patchResourceServerSide(target, dryRun, forceConflicts, fieldValidationDirective) + if err != nil { + logger.Debug("Error creating resource via patch", slog.Any("error", err)) + return err + } - return nil + logger.Debug("Created resource via patch") + return nil + }) } } @@ -952,6 +956,32 @@ func isIncompatibleServerError(err error) bool { return err.(*apierrors.StatusError).Status().Code == http.StatusUnsupportedMediaType } +// isServerSideRetryable checks if an error encountered during server-side apply +// should be retried. Currently, only ResourceQuota conflicts are considered retryable. +func isServerSideRetryable(err error) bool { + return isResourceQuotaConflict(err) +} + +// isResourceQuotaConflict checks if the error is a conflict error specifically caused by +// a ResourceQuota. This is used to determine if a retry should be attempted, +// since quota conflicts are typically transient and can be resolved by retrying. +func isResourceQuotaConflict(err error) bool { + if !apierrors.IsConflict(err) { + return false + } + + // Check the error message for the specific ResourceQuota conflict pattern. + // The error message from the ResourceQuota admission controller contains: + // "Operation cannot be fulfilled on resourcequotas" and "the object has been modified" + errMsg := err.Error() + if strings.Contains(errMsg, "Operation cannot be fulfilled on resourcequotas") && + strings.Contains(errMsg, "the object has been modified") { + return true + } + + return false +} + // getManagedFieldsManager returns the manager string. If one was set it will be returned. // Otherwise, one is calculated based on the name of the binary. func getManagedFieldsManager() string { diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index b5f1f5f12..6421a2c2d 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -53,6 +53,7 @@ import ( k8sfake "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest/fake" + "k8s.io/client-go/util/retry" cmdtesting "k8s.io/kubectl/pkg/cmd/testing" ) @@ -273,8 +274,30 @@ func TestCreate(t *testing.T) { }, ExpectedErrorContains: "Operation cannot be fulfilled on resourcequotas \"quota\": the object has been modified; " + "please apply your changes to the latest version and try again", + ExpectedActions: func() []string { // expect helm to retry on conflict, workaround for: https://github.com/kubernetes/kubernetes/issues/67761 + actions := make([]string, retry.DefaultRetry.Steps) + for i := range actions { + actions[i] = "/namespaces/default/pods/dolphin:PATCH" + } + return actions + }(), + }, + "Create fail: managed fields conflict (server-side apply)": { + Pods: newPodList("seal"), + ServerSideApply: true, + Callback: func(t *testing.T, _ testCase, _ []RequestResponseAction, req *http.Request) (*http.Response, error) { + t.Helper() + + // Return a generic 409 conflict (not quota-related) + // This simulates a managed fields conflict + return &http.Response{ + StatusCode: http.StatusConflict, + Request: req, + }, nil + }, + ExpectedErrorContains: "the server reported a conflict", ExpectedActions: []string{ - "/namespaces/default/pods/dolphin:PATCH", + "/namespaces/default/pods/seal:PATCH", }, }, } From ad93b7df635d032a71201206cc3490e76b83bef4 Mon Sep 17 00:00:00 2001 From: Lohit Kolluri Date: Wed, 15 Jul 2026 06:52:09 +0530 Subject: [PATCH 435/571] feat: honor SOURCE_DATE_EPOCH for chart archives (#32162) * feat: honor SOURCE_DATE_EPOCH for chart archives Add StampModTimes() on Chart struct to recursively set ModTime on all chart file entries. CLI reads SOURCE_DATE_EPOCH env var and wires through all 5 commands: package, install, upgrade, dep build, dep update. Removes unused v3 duplicate source_date_epoch.go (YAGNI). Inlines env var parsing to CLI layer from chart util. Signed-off-by: Lohit Kolluri * docs: document SOURCE_DATE_EPOCH in environment variables help Add a short entry for $SOURCE_DATE_EPOCH to the env var table shown in 'helm help'. The variable is honored by 'helm package', 'install', 'upgrade', 'dep build', and 'dep update' to produce reproducible chart archives. Full documentation belongs in helm-www. Signed-off-by: Lohit Kolluri * fix: adapt SourceDateEpoch to upstream refactored dep check Upstream/main restructured the dependency error handling in install.go and upgrade.go from a nested-if block (if DependencyUpdate) to an early-return pattern (if !DependencyUpdate). Adapt the feature's Manager construction to match the new code structure. Signed-off-by: Lohit Kolluri --------- Signed-off-by: Lohit Kolluri --- pkg/action/package.go | 7 +++ pkg/chart/v2/chart.go | 31 ++++++++++++ pkg/chart/v2/util/save_test.go | 34 +++++++++++++ pkg/cmd/dependency_build.go | 5 ++ pkg/cmd/dependency_update.go | 5 ++ pkg/cmd/install.go | 5 ++ pkg/cmd/package.go | 6 +++ pkg/cmd/root.go | 1 + pkg/cmd/source_date_epoch.go | 42 ++++++++++++++++ pkg/cmd/source_date_epoch_test.go | 82 +++++++++++++++++++++++++++++++ pkg/cmd/upgrade.go | 5 ++ pkg/downloader/manager.go | 11 ++++- 12 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 pkg/cmd/source_date_epoch.go create mode 100644 pkg/cmd/source_date_epoch_test.go diff --git a/pkg/action/package.go b/pkg/action/package.go index 002203f40..1b7901f17 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -23,6 +23,7 @@ import ( "os" "path/filepath" "syscall" + "time" "github.com/Masterminds/semver/v3" "golang.org/x/term" @@ -58,6 +59,8 @@ type Package struct { KeyFile string CaFile string InsecureSkipTLSVerify bool + // SourceDateEpoch, when set, normalizes chart timestamps for reproducible archives. + SourceDateEpoch *time.Time } const ( @@ -103,6 +106,10 @@ func (p *Package) Run(path string, _ map[string]any) (string, error) { ch.Metadata.AppVersion = p.AppVersion } + if p.SourceDateEpoch != nil { + ch.StampModTimes(*p.SourceDateEpoch) + } + if reqs := ac.MetaDependencies(); len(reqs) > 0 { if err := CheckDependencies(ch, reqs); err != nil { return "", err diff --git a/pkg/chart/v2/chart.go b/pkg/chart/v2/chart.go index 9d798b4de..9772754ce 100644 --- a/pkg/chart/v2/chart.go +++ b/pkg/chart/v2/chart.go @@ -177,6 +177,37 @@ func (ch *Chart) CRDObjects() []CRD { return crds } +// StampModTimes sets timestamps on the chart (and dependencies) to epoch. +// This is used for reproducible builds via SOURCE_DATE_EPOCH. +func (ch *Chart) StampModTimes(epoch time.Time) { + ch.ModTime = epoch + if len(ch.Schema) > 0 { + ch.SchemaModTime = epoch + } + if ch.Lock != nil { + ch.Lock.Generated = epoch + } + + for _, f := range ch.Raw { + if f != nil { + f.ModTime = epoch + } + } + for _, f := range ch.Templates { + if f != nil { + f.ModTime = epoch + } + } + for _, f := range ch.Files { + if f != nil { + f.ModTime = epoch + } + } + for _, dep := range ch.Dependencies() { + dep.StampModTimes(epoch) + } +} + func hasManifestExtension(fname string) bool { ext := filepath.Ext(fname) return strings.EqualFold(ext, ".yaml") || strings.EqualFold(ext, ".yml") || strings.EqualFold(ext, ".json") diff --git a/pkg/chart/v2/util/save_test.go b/pkg/chart/v2/util/save_test.go index 635158e9c..55c67f49e 100644 --- a/pkg/chart/v2/util/save_test.go +++ b/pkg/chart/v2/util/save_test.go @@ -231,6 +231,40 @@ func TestSavePreservesTimestamps(t *testing.T) { } } +func TestSaveWithSourceDateEpoch(t *testing.T) { + epoch := time.Unix(1609459200, 0).UTC() + tmp := t.TempDir() + c := &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: chart.APIVersionV2, + Name: "ahab", + Version: "1.2.3", + }, + Files: []*common.File{ + {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, + }, + Schema: []byte("{\n \"title\": \"Values\"\n}"), + } + + c.StampModTimes(epoch) + where, err := Save(c, tmp) + if err != nil { + t.Fatalf("Failed to save: %s", err) + } + + allHeaders, err := retrieveAllHeadersFromTar(where) + if err != nil { + t.Fatalf("Failed to parse tar: %v", err) + } + + expected := epoch.Round(time.Second) + for _, header := range allHeaders { + if !header.ModTime.Equal(expected) { + t.Fatalf("Expected SOURCE_DATE_EPOCH timestamp %v, got %v for %q", expected, header.ModTime, header.Name) + } + } +} + // We could refactor `load.go` to use this `retrieveAllHeadersFromTar` function // as well, so we are not duplicating components of the code which iterate // through the tar. diff --git a/pkg/cmd/dependency_build.go b/pkg/cmd/dependency_build.go index f572fbae1..85c385670 100644 --- a/pkg/cmd/dependency_build.go +++ b/pkg/cmd/dependency_build.go @@ -55,6 +55,10 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command { if len(args) > 0 { chartpath = filepath.Clean(args[0]) } + sourceDateEpoch, err := sourceDateEpochFromEnv() + if err != nil { + return err + } registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { @@ -72,6 +76,7 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command { RepositoryCache: settings.RepositoryCache, ContentCache: settings.ContentCache, Debug: settings.Debug, + SourceDateEpoch: sourceDateEpoch, } if client.Verify { man.Verify = downloader.VerifyIfPossible diff --git a/pkg/cmd/dependency_update.go b/pkg/cmd/dependency_update.go index a7131e587..0db36553c 100644 --- a/pkg/cmd/dependency_update.go +++ b/pkg/cmd/dependency_update.go @@ -58,6 +58,10 @@ func newDependencyUpdateCmd(_ *action.Configuration, out io.Writer) *cobra.Comma if len(args) > 0 { chartpath = filepath.Clean(args[0]) } + sourceDateEpoch, err := sourceDateEpochFromEnv() + if err != nil { + return err + } registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { @@ -75,6 +79,7 @@ func newDependencyUpdateCmd(_ *action.Configuration, out io.Writer) *cobra.Comma RepositoryCache: settings.RepositoryCache, ContentCache: settings.ContentCache, Debug: settings.Debug, + SourceDateEpoch: sourceDateEpoch, } if client.Verify { man.Verify = downloader.VerifyAlways diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index c7aa2c542..b935ed4d0 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -297,6 +297,10 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options } if req := ac.MetaDependencies(); len(req) > 0 { + sourceDateEpoch, err := sourceDateEpochFromEnv() + if err != nil { + return nil, err + } // If CheckDependencies returns an error, we have unfulfilled dependencies. // As of Helm 2.4.0, this is treated as a stopping condition: // https://github.com/helm/helm/issues/2209 @@ -315,6 +319,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options ContentCache: settings.ContentCache, Debug: settings.Debug, RegistryClient: client.GetRegistryClient(), + SourceDateEpoch: sourceDateEpoch, } if err := man.Update(); err != nil { return nil, err diff --git a/pkg/cmd/package.go b/pkg/cmd/package.go index 46133d971..7e240c8d0 100644 --- a/pkg/cmd/package.go +++ b/pkg/cmd/package.go @@ -59,6 +59,11 @@ func newPackageCmd(out io.Writer) *cobra.Command { if len(args) == 0 { return errors.New("need at least one argument, the path to the chart") } + sourceDateEpoch, err := sourceDateEpochFromEnv() + if err != nil { + return err + } + client.SourceDateEpoch = sourceDateEpoch if client.Sign { if client.Key == "" { return errors.New("--key is required for signing a package") @@ -101,6 +106,7 @@ func newPackageCmd(out io.Writer) *cobra.Command { RepositoryConfig: settings.RepositoryConfig, RepositoryCache: settings.RepositoryCache, ContentCache: settings.ContentCache, + SourceDateEpoch: sourceDateEpoch, } if err := downloadManager.Update(); err != nil { diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 7ee71f4ef..28798de5c 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -84,6 +84,7 @@ Environment variables: | $HELM_QPS | set the Queries Per Second in cases where a high number of calls exceed the option for higher burst values | | $HELM_COLOR | set color output mode. Allowed values: never, always, auto (default: never) | | $NO_COLOR | set to any non-empty value to disable all colored output (overrides $HELM_COLOR) | +| $SOURCE_DATE_EPOCH | set a Unix timestamp for reproducible chart archives | Helm stores cache, configuration, and data based on the following configuration order: diff --git a/pkg/cmd/source_date_epoch.go b/pkg/cmd/source_date_epoch.go new file mode 100644 index 000000000..6f73e3b30 --- /dev/null +++ b/pkg/cmd/source_date_epoch.go @@ -0,0 +1,42 @@ +/* +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 cmd + +import ( + "errors" + "fmt" + "os" + "strconv" + "time" +) + +// sourceDateEpochFromEnv returns SOURCE_DATE_EPOCH when set, or nil when unset. +func sourceDateEpochFromEnv() (*time.Time, error) { + epochStr, ok := os.LookupEnv("SOURCE_DATE_EPOCH") + if !ok || epochStr == "" { + return nil, nil + } + epoch, err := strconv.ParseInt(epochStr, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid SOURCE_DATE_EPOCH: %w", err) + } + if epoch < 0 { + return nil, errors.New("invalid SOURCE_DATE_EPOCH: must not be negative") + } + t := time.Unix(epoch, 0).UTC() + return &t, nil +} diff --git a/pkg/cmd/source_date_epoch_test.go b/pkg/cmd/source_date_epoch_test.go new file mode 100644 index 000000000..719de7c90 --- /dev/null +++ b/pkg/cmd/source_date_epoch_test.go @@ -0,0 +1,82 @@ +/* +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 cmd + +import ( + "testing" + "time" +) + +func TestSourceDateEpochFromEnv(t *testing.T) { + t.Setenv("SOURCE_DATE_EPOCH", "1609459200") + + got, err := sourceDateEpochFromEnv() + if err != nil { + t.Fatalf("sourceDateEpochFromEnv() error: %v", err) + } + if got == nil { + t.Fatal("expected non-nil epoch") + } + want := time.Unix(1609459200, 0).UTC() + if !got.Equal(want) { + t.Fatalf("expected %v, got %v", want, *got) + } +} + +func TestSourceDateEpochFromEnvUnset(t *testing.T) { + t.Setenv("SOURCE_DATE_EPOCH", "") + + got, err := sourceDateEpochFromEnv() + if err != nil { + t.Fatalf("sourceDateEpochFromEnv() error: %v", err) + } + if got != nil { + t.Fatalf("expected nil epoch, got %v", *got) + } +} + +func TestSourceDateEpochFromEnvInvalid(t *testing.T) { + t.Setenv("SOURCE_DATE_EPOCH", "not-a-number") + + if _, err := sourceDateEpochFromEnv(); err == nil { + t.Fatal("expected error for invalid SOURCE_DATE_EPOCH") + } +} + +func TestSourceDateEpochFromEnvNegative(t *testing.T) { + t.Setenv("SOURCE_DATE_EPOCH", "-1") + + if _, err := sourceDateEpochFromEnv(); err == nil { + t.Fatal("expected error for negative SOURCE_DATE_EPOCH") + } +} + +func TestSourceDateEpochFromEnvZero(t *testing.T) { + t.Setenv("SOURCE_DATE_EPOCH", "0") + + got, err := sourceDateEpochFromEnv() + if err != nil { + t.Fatalf("sourceDateEpochFromEnv() error: %v", err) + } + if got == nil { + t.Fatal("expected non-nil epoch") + } + want := time.Unix(0, 0).UTC() + if !got.Equal(want) { + t.Fatalf("expected %v, got %v", want, *got) + } +} diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index a6aa96129..c038a2149 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -204,6 +204,10 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return err } if req := ac.MetaDependencies(); len(req) > 0 { + sourceDateEpoch, err := sourceDateEpochFromEnv() + if err != nil { + return err + } if err := action.CheckDependencies(ch, req); err != nil { err = fmt.Errorf("an error occurred while checking for chart dependencies. You may need to run 'helm dependency build' to fetch missing dependencies: %w", err) if !client.DependencyUpdate { @@ -219,6 +223,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { RepositoryCache: settings.RepositoryCache, ContentCache: settings.ContentCache, Debug: settings.Debug, + SourceDateEpoch: sourceDateEpoch, } if err := man.Update(); err != nil { return err diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 1203eb991..a1fa0ddbd 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -29,6 +29,7 @@ import ( "regexp" "strings" "sync" + "time" "github.com/Masterminds/semver/v3" "sigs.k8s.io/yaml" @@ -78,6 +79,8 @@ type Manager struct { // ContentCache is a location where a cache of charts can be stored ContentCache string + // SourceDateEpoch, when set, normalizes chart timestamps for reproducible archives. + SourceDateEpoch *time.Time } // Build rebuilds a local charts directory from a lockfile. @@ -303,7 +306,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { if m.Debug { fmt.Fprintf(m.Out, "Archiving %s from repo %s\n", dep.Name, dep.Repository) } - ver, err := tarFromLocalDir(m.ChartPath, dep.Name, dep.Repository, dep.Version, tmpPath) + ver, err := tarFromLocalDir(m.ChartPath, dep.Name, dep.Repository, dep.Version, tmpPath, m.SourceDateEpoch) if err != nil { saveError = err break @@ -867,7 +870,7 @@ func writeLock(chartpath string, lock *chart.Lock, legacyLockfile bool) error { } // archive a dep chart from local directory and save it into destPath -func tarFromLocalDir(chartpath, name, repo, version, destPath string) (string, error) { +func tarFromLocalDir(chartpath, name, repo, version, destPath string, sourceDateEpoch *time.Time) (string, error) { if !strings.HasPrefix(repo, "file://") { return "", fmt.Errorf("wrong format: chart %s repository %s", name, repo) } @@ -882,6 +885,10 @@ func tarFromLocalDir(chartpath, name, repo, version, destPath string) (string, e return "", err } + if sourceDateEpoch != nil { + ch.StampModTimes(*sourceDateEpoch) + } + constraint, err := semver.NewConstraint(version) if err != nil { return "", fmt.Errorf("dependency %s has an invalid version/constraint format: %w", name, err) From caac755f199adf5ac4fbbc8c46a741d004ed7276 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 10 Jul 2026 00:24:50 +0200 Subject: [PATCH 436/571] chore(internal): refactor: convert tests to testify assert/require part 3 refactor: convert tests to testify assert/require in internal/chart/v3/lint/rules Signed-off-by: Matthieu MOREL --- internal/chart/v3/chart_test.go | 10 ++-- .../chart/v3/lint/rules/dependencies_test.go | 23 ++++------ internal/chart/v3/lint/rules/values_test.go | 39 +++++----------- internal/chart/v3/loader/load_test.go | 5 +- internal/chart/v3/metadata_test.go | 20 +++----- internal/chart/v3/util/compatible_test.go | 10 ++-- internal/plugin/sign_test.go | 46 ++++++------------- 7 files changed, 53 insertions(+), 100 deletions(-) diff --git a/internal/chart/v3/chart_test.go b/internal/chart/v3/chart_test.go index d5d066d9b..554dde2c1 100644 --- a/internal/chart/v3/chart_test.go +++ b/internal/chart/v3/chart_test.go @@ -21,6 +21,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/chart/common" ) @@ -76,15 +77,12 @@ func TestSaveChartNoRawData(t *testing.T) { } is := assert.New(t) + req := require.New(t) data, err := json.Marshal(chrt) - if err != nil { - t.Fatal(err) - } + req.NoError(err) res := &Chart{} - if err := json.Unmarshal(data, res); err != nil { - t.Fatal(err) - } + req.NoError(json.Unmarshal(data, res)) is.Equal([]*common.File(nil), res.Raw) } diff --git a/internal/chart/v3/lint/rules/dependencies_test.go b/internal/chart/v3/lint/rules/dependencies_test.go index 31fd0ef33..ae5882110 100644 --- a/internal/chart/v3/lint/rules/dependencies_test.go +++ b/internal/chart/v3/lint/rules/dependencies_test.go @@ -19,6 +19,9 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/internal/chart/v3" "helm.sh/helm/v4/internal/chart/v3/lint/support" chartutil "helm.sh/helm/v4/internal/chart/v3/util" @@ -63,17 +66,13 @@ func chartWithBadDependencies() chart.Chart { func TestValidateDependencyInChartsDir(t *testing.T) { c := chartWithBadDependencies() - if err := validateDependencyInChartsDir(&c); err == nil { - t.Error("chart should have been flagged for missing deps in chart directory") - } + assert.Error(t, validateDependencyInChartsDir(&c), "chart should have been flagged for missing deps in chart directory") } func TestValidateDependencyInMetadata(t *testing.T) { c := chartWithBadDependencies() - if err := validateDependencyInMetadata(&c); err == nil { - t.Error("chart should have been flagged for missing deps in chart metadata") - } + assert.Error(t, validateDependencyInMetadata(&c), "chart should have been flagged for missing deps in chart metadata") } func TestValidateDependenciesUnique(t *testing.T) { @@ -131,9 +130,7 @@ func TestValidateDependenciesUnique(t *testing.T) { } for _, tt := range tests { - if err := validateDependenciesUnique(&tt.chart); err == nil { - t.Error("chart should have been flagged for dependency shadowing") - } + assert.Error(t, validateDependenciesUnique(&tt.chart), "chart should have been flagged for dependency shadowing") } } @@ -141,15 +138,11 @@ func TestDependencies(t *testing.T) { tmp := t.TempDir() c := chartWithBadDependencies() - err := chartutil.SaveDir(&c, tmp) - if err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(&c, tmp)) linter := support.Linter{ChartDir: filepath.Join(tmp, c.Metadata.Name)} Dependencies(&linter) - if l := len(linter.Messages); l != 2 { - t.Errorf("expected 2 linter errors for bad chart dependencies. Got %d.", l) + if !assert.Len(t, linter.Messages, 2, "expected 2 linter errors for bad chart dependencies") { for i, msg := range linter.Messages { t.Logf("Message: %d, Error: %#v", i, msg) } diff --git a/internal/chart/v3/lint/rules/values_test.go b/internal/chart/v3/lint/rules/values_test.go index 470904049..c87607f2d 100644 --- a/internal/chart/v3/lint/rules/values_test.go +++ b/internal/chart/v3/lint/rules/values_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/internal/test/ensure" ) @@ -56,9 +57,7 @@ func TestValidateValuesYamlNotDirectory(t *testing.T) { defer os.Remove(nonExistingValuesFilePath) err := validateValuesFileExistence(nonExistingValuesFilePath) - if err == nil { - t.Error("validateValuesFileExistence to return a linter error, got no error") - } + assert.Error(t, err, "validateValuesFileExistence to return a linter error, got no error") } func TestValidateValuesFileWellFormed(t *testing.T) { @@ -67,9 +66,7 @@ func TestValidateValuesFileWellFormed(t *testing.T) { ` tmpdir := ensure.TempFile(t, "values.yaml", []byte(badYaml)) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]any{}, false); err == nil { - t.Fatal("expected values file to fail parsing") - } + require.Error(t, validateValuesFile(valfile, map[string]any{}, false), "expected values file to fail parsing") } func TestValidateValuesFileSchema(t *testing.T) { @@ -78,9 +75,7 @@ func TestValidateValuesFileSchema(t *testing.T) { createTestingSchema(t, tmpdir) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]any{}, false); err != nil { - t.Fatalf("Failed validation with %s", err) - } + require.NoError(t, validateValuesFile(valfile, map[string]any{}, false), "Failed validation") } func TestValidateValuesFileSchemaFailure(t *testing.T) { @@ -91,8 +86,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) { valfile := filepath.Join(tmpdir, "values.yaml") - err := validateValuesFile(valfile, map[string]any{}, false) - assert.ErrorContains(t, err, "- at '/username': got number, want string") + assert.ErrorContains(t, validateValuesFile(valfile, map[string]any{}, false), "- at '/username': got number, want string") } func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T) { @@ -103,10 +97,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T valfile := filepath.Join(tmpdir, "values.yaml") - err := validateValuesFile(valfile, map[string]any{}, true) - if err != nil { - t.Fatal("expected values file to pass parsing because of skipSchemaValidation") - } + require.NoError(t, validateValuesFile(valfile, map[string]any{}, true), "expected values file to pass parsing because of skipSchemaValidation") } func TestValidateValuesFileSchemaOverrides(t *testing.T) { @@ -118,9 +109,7 @@ func TestValidateValuesFileSchemaOverrides(t *testing.T) { createTestingSchema(t, tmpdir) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, overrides, false); err != nil { - t.Fatalf("Failed validation with %s", err) - } + require.NoError(t, validateValuesFile(valfile, overrides, false), "Failed validation") } func TestValidateValuesFile(t *testing.T) { @@ -157,12 +146,10 @@ func TestValidateValuesFile(t *testing.T) { err := validateValuesFile(valfile, tt.overrides, false) - switch { - case err != nil && tt.errorMessage == "": - t.Errorf("Failed validation with %s", err) - case err == nil && tt.errorMessage != "": - t.Error("expected values file to fail parsing") - case err != nil && tt.errorMessage != "": + if tt.errorMessage == "" { + require.NoError(t, err, "Failed validation") + } else { + require.Error(t, err, "expected values file to fail parsing") assert.ErrorContains(t, err, tt.errorMessage, "Failed with unexpected error") } }) @@ -172,8 +159,6 @@ func TestValidateValuesFile(t *testing.T) { func createTestingSchema(t *testing.T, dir string) string { t.Helper() schemafile := filepath.Join(dir, "values.schema.json") - if err := os.WriteFile(schemafile, []byte(testSchema), 0o700); err != nil { - t.Fatalf("Failed to write schema to tmpdir: %s", err) - } + require.NoError(t, os.WriteFile(schemafile, []byte(testSchema), 0o700), "Failed to write schema to tmpdir") return schemafile } diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index 1c5845da6..5caec7815 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -31,6 +31,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" chart "helm.sh/helm/v4/internal/chart/v3" @@ -690,9 +691,7 @@ func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) { if dep.Templates[0].Name != "templates/alpine-pod.yaml" { t.Errorf("Unexpected template: %s", dep.Templates[0].Name) } - if len(dep.Templates[0].Data) == 0 { - t.Error("No template data.") - } + assert.NotEmpty(t, dep.Templates[0].Data, "No template data.") if len(dep.Files) != 1 { t.Fatalf("Expected 1 Files, got %d", len(dep.Files)) } diff --git a/internal/chart/v3/metadata_test.go b/internal/chart/v3/metadata_test.go index 5f88552e9..91b917980 100644 --- a/internal/chart/v3/metadata_test.go +++ b/internal/chart/v3/metadata_test.go @@ -16,8 +16,10 @@ limitations under the License. package v3 import ( - "errors" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestValidate(t *testing.T) { @@ -192,21 +194,13 @@ func TestValidate(t *testing.T) { for _, tt := range tests { result := tt.md.Validate() - if !errors.Is(result, tt.err) { - t.Errorf("expected %q, got %q in test %q", tt.err, result, tt.name) - } + assert.ErrorIsf(t, result, tt.err, "expected %q, got %q in test %q", tt.err, result, tt.name) } } func TestValidate_sanitize(t *testing.T) { md := &Metadata{APIVersion: "3", Name: "test", Version: "1.0", Description: "\adescr\u0081iption\rtest", Maintainers: []*Maintainer{{Name: "\r"}}} - if err := md.Validate(); err != nil { - t.Fatalf("unexpected error: %s", err) - } - if md.Description != "description test" { - t.Fatalf("description was not sanitized: %q", md.Description) - } - if md.Maintainers[0].Name != " " { - t.Fatal("maintainer name was not sanitized") - } + require.NoError(t, md.Validate()) + require.Equalf(t, "description test", md.Description, "description was not sanitized: %q", md.Description) + require.Equal(t, " ", md.Maintainers[0].Name, "maintainer name was not sanitized") } diff --git a/internal/chart/v3/util/compatible_test.go b/internal/chart/v3/util/compatible_test.go index e17d33e35..b210d3dd8 100644 --- a/internal/chart/v3/util/compatible_test.go +++ b/internal/chart/v3/util/compatible_test.go @@ -17,7 +17,11 @@ limitations under the License. // Package version represents the current version of the project. package util -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" +) func TestIsCompatibleRange(t *testing.T) { tests := []struct { @@ -36,8 +40,6 @@ func TestIsCompatibleRange(t *testing.T) { } for _, tt := range tests { - if IsCompatibleRange(tt.constraint, tt.ver) != tt.expected { - t.Errorf("expected constraint %s to be %v for %s", tt.constraint, tt.expected, tt.ver) - } + assert.Equalf(t, tt.expected, IsCompatibleRange(tt.constraint, tt.ver), "expected constraint %s to be %v for %s", tt.constraint, tt.expected, tt.ver) } } diff --git a/internal/plugin/sign_test.go b/internal/plugin/sign_test.go index 7e97a82aa..d6fd4c548 100644 --- a/internal/plugin/sign_test.go +++ b/internal/plugin/sign_test.go @@ -18,9 +18,11 @@ package plugin import ( "os" "path/filepath" - "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/provenance" ) @@ -28,9 +30,7 @@ func TestSignPlugin(t *testing.T) { // Create a test plugin directory tempDir := t.TempDir() pluginDir := filepath.Join(tempDir, "test-plugin") - if err := os.MkdirAll(pluginDir, 0o755); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(pluginDir, 0o755)) // Create a plugin.yaml file pluginYAML := `apiVersion: v1 @@ -41,16 +41,12 @@ version: 1.0.0 runtimeConfig: platformCommand: - command: echo` - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginYAML), 0o644); err != nil { - t.Fatal(err) - } + require.NoError(t, os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginYAML), 0o644)) // Create a tarball tarballPath := filepath.Join(tempDir, "test-plugin.tgz") tarFile, err := os.Create(tarballPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if err := CreatePluginTarball(pluginDir, "test-plugin", tarFile); err != nil { tarFile.Close() t.Fatal(err) @@ -60,39 +56,25 @@ runtimeConfig: // Create a test key for signing keyring := "../../pkg/cmd/testdata/helm-test-key.secret" signer, err := provenance.NewFromKeyring(keyring, "helm-test") - if err != nil { - t.Fatal(err) - } - if err := signer.DecryptKey(func(_ string) ([]byte, error) { + require.NoError(t, err) + require.NoError(t, signer.DecryptKey(func(_ string) ([]byte, error) { return []byte(""), nil - }); err != nil { - t.Fatal(err) - } + })) // Read the tarball data tarballData, err := os.ReadFile(tarballPath) - if err != nil { - t.Fatalf("failed to read tarball: %v", err) - } + require.NoError(t, err, "failed to read tarball") // Sign the plugin tarball sig, err := SignPlugin(tarballData, filepath.Base(tarballPath), signer) - if err != nil { - t.Fatalf("failed to sign plugin: %v", err) - } + require.NoError(t, err, "failed to sign plugin") // Verify the signature contains the expected content - if !strings.Contains(sig, "-----BEGIN PGP SIGNED MESSAGE-----") { - t.Error("signature does not contain PGP header") - } + assert.Contains(t, sig, "-----BEGIN PGP SIGNED MESSAGE-----", "signature does not contain PGP header") // Verify the tarball hash is in the signature expectedHash, err := provenance.DigestFile(tarballPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // The signature should contain the tarball hash - if !strings.Contains(sig, "sha256:"+expectedHash) { - t.Errorf("signature does not contain expected tarball hash: sha256:%s", expectedHash) - } + assert.Contains(t, sig, "sha256:"+expectedHash, "signature does not contain expected tarball hash: sha256:%s", expectedHash) } From 96a25ab4f86c2c78de2acfe45a69c0a40055aa3c Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Wed, 15 Jul 2026 16:48:15 -0700 Subject: [PATCH 437/571] fix: 'gocritic: filepathJoin path seperator' lint error Signed-off-by: George Jenkins --- pkg/helmpath/lazypath_darwin_test.go | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/pkg/helmpath/lazypath_darwin_test.go b/pkg/helmpath/lazypath_darwin_test.go index 143750f04..8d61334f8 100644 --- a/pkg/helmpath/lazypath_darwin_test.go +++ b/pkg/helmpath/lazypath_darwin_test.go @@ -36,13 +36,12 @@ func TestDataPath(t *testing.T) { os.Unsetenv(xdg.DataHomeEnvVar) expected := filepath.Join(homedir.HomeDir(), "Library", appName, testFile) - assert.Equal(t, expected, lazy.dataPath(testFile)) - t.Setenv(xdg.DataHomeEnvVar, "/tmp") - - expected = filepath.Join("/tmp", appName, testFile) + tmpDir := t.TempDir() + t.Setenv(xdg.DataHomeEnvVar, tmpDir) + expected = filepath.Join(tmpDir, appName, testFile) assert.Equal(t, expected, lazy.dataPath(testFile)) } @@ -50,13 +49,12 @@ func TestConfigPath(t *testing.T) { os.Unsetenv(xdg.ConfigHomeEnvVar) expected := filepath.Join(homedir.HomeDir(), "Library", "Preferences", appName, testFile) - assert.Equal(t, expected, lazy.configPath(testFile)) - t.Setenv(xdg.ConfigHomeEnvVar, "/tmp") - - expected = filepath.Join("/tmp", appName, testFile) + tmpDir := t.TempDir() + t.Setenv(xdg.ConfigHomeEnvVar, tmpDir) + expected = filepath.Join(tmpDir, appName, testFile) assert.Equal(t, expected, lazy.configPath(testFile)) } @@ -64,12 +62,11 @@ func TestCachePath(t *testing.T) { os.Unsetenv(xdg.CacheHomeEnvVar) expected := filepath.Join(homedir.HomeDir(), "Library", "Caches", appName, testFile) - assert.Equal(t, expected, lazy.cachePath(testFile)) - t.Setenv(xdg.CacheHomeEnvVar, "/tmp") - - expected = filepath.Join("/tmp", appName, testFile) + tmpDir := t.TempDir() + t.Setenv(xdg.CacheHomeEnvVar, tmpDir) + expected = filepath.Join(tmpDir, appName, testFile) assert.Equal(t, expected, lazy.cachePath(testFile)) } From 9f3c89a4b50e99a46c23b6942b87b205bdd8c33f Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Thu, 16 Jul 2026 09:29:59 +0200 Subject: [PATCH 438/571] ci: auto-label PRs targeting main with v4.x Mirrors the dev-v3 labeling workflow from #32340, reusing the existing v4.x label since main tracks the upcoming v4 release. Signed-off-by: Benoit Tigeot --- .github/labeler.yml | 2 ++ .github/workflows/label-pr.yml | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/label-pr.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..46d20af9c --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,2 @@ +"v4.x": + - base-branch: "main" diff --git a/.github/workflows/label-pr.yml b/.github/workflows/label-pr.yml new file mode 100644 index 000000000..5c2b2fa17 --- /dev/null +++ b/.github/workflows/label-pr.yml @@ -0,0 +1,16 @@ +name: Label PRs +on: + pull_request_target: + branches: [main] + types: [opened, reopened, edited, synchronize] +permissions: {} +jobs: + label: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # pin@v6.2.0 + with: + sync-labels: true From 8cd3a48c34136f14fa6ba60554ef5ab0e12625be Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Thu, 16 Jul 2026 11:14:59 -0700 Subject: [PATCH 439/571] chore: Add AI Vendor specific paths to `.gitignore` Signed-off-by: George Jenkins --- .gitignore | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 2209e9809..08c6b535e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ .DS_Store .coverage/ .idea -.claude .vimrc .vscode/ .devcontainer/ @@ -11,7 +10,14 @@ _dist/ _dist_versions/ bin/ vendor/ +.pre-commit-config.yaml + # Ignores charts pulled for dependency build tests cmd/helm/testdata/testcharts/issue-7233/charts/* pkg/cmd/testdata/testcharts/issue-7233/charts/* -.pre-commit-config.yaml + +# AI Vendor specific paths +CLAUDE.md +.claude/ +.codex/ +.cursor/ From eeb37edfeafffdb562b97774f85a394332cf58dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 21:32:51 +0000 Subject: [PATCH 440/571] chore(deps): bump github/codeql-action/autobuild from 4.37.0 to 4.37.1 Bumps [github/codeql-action/autobuild](https://github.com/github/codeql-action) from 4.37.0 to 4.37.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/99df26d4f13ea111d4ec1a7dddef6063f76b97e9...7188fc363630916deb702c7fdcf4e481b751f97a) --- updated-dependencies: - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e87ec8ec2..52c045e85 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # pinv4.37.0 + uses: github/codeql-action/autobuild@7188fc363630916deb702c7fdcf4e481b751f97a # pinv4.37.1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl From a494169853d1e6f752eb838fedbb00d1a8593b66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 05:44:01 -0600 Subject: [PATCH 441/571] chore(deps): bump actions/setup-go from 6.5.0 to 7.0.0 (#32376) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.5.0 to 7.0.0. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/924ae3a1cded613372ab5595356fb5720e22ba16...b7ad1dad31e06c5925ef5d2fc7ad053ef454303e) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-test.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/release.yml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index b4ce89f26..066adf1ce 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -22,7 +22,7 @@ jobs: - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # pin@6.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # pin@7.0.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 6355db230..c5312e936 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -17,7 +17,7 @@ jobs: - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # pin@6.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # pin@7.0.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index c7c9b3d53..0aee4c01c 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -25,7 +25,7 @@ jobs: - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # pin@6.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # pin@7.0.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ee2612917..3bed1e12a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # pin@6.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # pin@7.0.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true @@ -93,7 +93,7 @@ jobs: run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # pin@6.5.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # pin@7.0.0 with: go-version: '${{ env.GOLANG_VERSION }}' check-latest: true From 06b95860dc2419062834a5e59921569676b45031 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 05:44:39 -0600 Subject: [PATCH 442/571] chore(deps): bump github/codeql-action/upload-sarif (#32378) Bumps [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) from 4.37.0 to 4.37.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/99df26d4f13ea111d4ec1a7dddef6063f76b97e9...7188fc363630916deb702c7fdcf4e481b751f97a) --- updated-dependencies: - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.37.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 343669859..3a8410ced 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1 with: sarif_file: results.sarif From 0563162dd95dcff0f7b1ac09e0c784ae21b72245 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Fri, 17 Jul 2026 05:51:11 -0600 Subject: [PATCH 443/571] chore(deps): bump github/codeql-action init and analyze to 4.37.1 Signed-off-by: Terry Howe --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 52c045e85..b0a03b342 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # pinv4.37.0 + uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # pinv4.37.1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # pinv4.37.0 + uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # pinv4.37.1 From 5a30c7ae8504218fd68d87c5a7d929d0a3bb3891 Mon Sep 17 00:00:00 2001 From: Solomon Wakhungu <65043605+1solomonwakhungu@users.noreply.github.com> Date: Sun, 12 Jul 2026 23:00:17 -0500 Subject: [PATCH 444/571] fix(scripts): add cache-busting to get-helm-3 version check The get-helm-3 script queries get.helm.sh/helm3-latest-version to determine the latest release. The CDN serving this file has no Cache-Control header, so edge servers can serve stale cached responses for hours after a new release. This causes non-deterministic behavior: machines running get-helm-3 at the same time can install different versions depending on which CDN edge node they hit. Fix: Append a timestamp query string (?ts=1783837438) to bust CDN caches, and add Cache-Control: no-cache header to both curl and wget requests. Fixes #32329 Signed-off-by: Solomon Wakhungu <65043605+1solomonwakhungu@users.noreply.github.com> --- scripts/get-helm-3 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/get-helm-3 b/scripts/get-helm-3 index 5f265a52f..dad66ab20 100755 --- a/scripts/get-helm-3 +++ b/scripts/get-helm-3 @@ -114,12 +114,14 @@ verifySupported() { checkDesiredVersion() { if [ "x$DESIRED_VERSION" == "x" ]; then # Get tag from release URL - local latest_release_url="https://get.helm.sh/helm3-latest-version" + # Append a cache-busting query string to avoid CDN edge servers + # serving stale version files after a new release. + local latest_release_url="https://get.helm.sh/helm3-latest-version?ts=$(date +%s)" local latest_release_response="" if [ "${HAS_CURL}" == "true" ]; then - latest_release_response=$( curl -L --silent --show-error --fail "$latest_release_url" 2>&1 || true ) + latest_release_response=$( curl -L --silent --show-error --fail -H "Cache-Control: no-cache" "$latest_release_url" 2>&1 || true ) elif [ "${HAS_WGET}" == "true" ]; then - latest_release_response=$( wget "$latest_release_url" -q -O - 2>&1 || true ) + latest_release_response=$( wget "$latest_release_url" --header="Cache-Control: no-cache" -q -O - 2>&1 || true ) fi TAG=$( echo "$latest_release_response" | grep '^v[0-9]' ) if [ "x$TAG" == "x" ]; then From a2b1a60d51223254bd9c2f08cf3f411eb9d9fc58 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Sat, 18 Jul 2026 13:28:03 +0200 Subject: [PATCH 445/571] fix: enhance error handling and improve test assertions (#32352) Signed-off-by: Matthieu MOREL --- .golangci.yml | 4 +-- .../chart/v3/lint/rules/deprecations_test.go | 8 ++---- internal/plugin/runtime_subprocess.go | 7 +++-- internal/sympath/walk.go | 9 +++--- pkg/chart/v2/lint/rules/deprecations_test.go | 13 +++++---- pkg/chart/v2/lint/rules/template_test.go | 10 ++++--- pkg/chart/v2/util/dependencies_test.go | 10 +++---- pkg/cmd/plugin_test.go | 28 ++++++------------- pkg/kube/client.go | 7 +++-- pkg/provenance/sign_test.go | 8 ++---- pkg/registry/transport.go | 3 +- pkg/repo/v1/index.go | 4 +-- pkg/strvals/parser.go | 22 +++++++-------- 13 files changed, 63 insertions(+), 70 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 6727d3001..5b6d7cf8e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -74,9 +74,9 @@ linters: errorlint: # Check for plain type assertions and type switches. - asserts: false + asserts: true # Check for plain error comparisons. - comparison: false + comparison: true exhaustive: default-signifies-exhaustive: true diff --git a/internal/chart/v3/lint/rules/deprecations_test.go b/internal/chart/v3/lint/rules/deprecations_test.go index a255abf50..55c454118 100644 --- a/internal/chart/v3/lint/rules/deprecations_test.go +++ b/internal/chart/v3/lint/rules/deprecations_test.go @@ -29,14 +29,12 @@ func TestValidateNoDeprecations(t *testing.T) { Kind: "Deployment", } err := validateNoDeprecations(deprecated, nil) - var depErr deprecatedAPIError require.Error(t, err, "Expected deprecated extension to be flagged") + var depErr deprecatedAPIError require.ErrorAs(t, err, &depErr, "Expected error to be of type deprecatedAPIError") require.NotEmpty(t, depErr.Message, "Expected error message to be non-blank: %v", err) - - err = validateNoDeprecations(&k8sYamlStruct{ + assert.NoError(t, validateNoDeprecations(&k8sYamlStruct{ APIVersion: "v1", Kind: "Pod", - }, nil) - assert.NoError(t, err, "Expected a v1 Pod to not be deprecated") + }, nil), "Expected a v1 Pod to not be deprecated") } diff --git a/internal/plugin/runtime_subprocess.go b/internal/plugin/runtime_subprocess.go index b921587c1..2dbe220d9 100644 --- a/internal/plugin/runtime_subprocess.go +++ b/internal/plugin/runtime_subprocess.go @@ -18,6 +18,7 @@ package plugin import ( "bytes" "context" + "errors" "fmt" "io" "log/slog" @@ -152,7 +153,8 @@ func (r *SubprocessPluginRuntime) InvokeHook(event string) error { slog.Debug("executing plugin hook command", slog.String("pluginName", r.metadata.Name), slog.String("command", cmd.String())) if err := cmd.Run(); err != nil { - if eerr, ok := err.(*exec.ExitError); ok { + var eerr *exec.ExitError + if errors.As(err, &eerr) { os.Stderr.Write(eerr.Stderr) return fmt.Errorf("plugin %s hook for %q exited with error", event, r.metadata.Name) } @@ -166,7 +168,8 @@ func (r *SubprocessPluginRuntime) InvokeHook(event string) error { // then replace the other three with a call to this func func executeCmd(prog *exec.Cmd, pluginName string) error { if err := prog.Run(); err != nil { - if eerr, ok := err.(*exec.ExitError); ok { + var eerr *exec.ExitError + if errors.As(err, &eerr) { slog.Debug( "plugin execution failed", slog.String("pluginName", pluginName), diff --git a/internal/sympath/walk.go b/internal/sympath/walk.go index 812bb68ce..b54b97ad4 100644 --- a/internal/sympath/walk.go +++ b/internal/sympath/walk.go @@ -21,6 +21,7 @@ limitations under the License. package sympath import ( + "errors" "fmt" "log/slog" "os" @@ -40,7 +41,7 @@ func Walk(root string, walkFn filepath.WalkFunc) error { } else { err = symwalk(root, info, walkFn) } - if err == filepath.SkipDir { + if errors.Is(err, filepath.SkipDir) { return nil } return err @@ -75,7 +76,7 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error { if info, err = os.Lstat(resolved); err != nil { return err } - if err := symwalk(path, info, walkFn); err != nil && err != filepath.SkipDir { + if err := symwalk(path, info, walkFn); err != nil && !errors.Is(err, filepath.SkipDir) { return err } return nil @@ -98,13 +99,13 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error { filename := filepath.Join(path, name) fileInfo, err := os.Lstat(filename) if err != nil { - if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { + if err := walkFn(filename, fileInfo, err); err != nil && !errors.Is(err, filepath.SkipDir) { return err } } else { err = symwalk(filename, fileInfo, walkFn) if err != nil { - if (!fileInfo.IsDir() && !IsSymlink(fileInfo)) || err != filepath.SkipDir { + if (!fileInfo.IsDir() && !IsSymlink(fileInfo)) || !errors.Is(err, filepath.SkipDir) { return err } } diff --git a/pkg/chart/v2/lint/rules/deprecations_test.go b/pkg/chart/v2/lint/rules/deprecations_test.go index 86643f1c9..fdeb47e91 100644 --- a/pkg/chart/v2/lint/rules/deprecations_test.go +++ b/pkg/chart/v2/lint/rules/deprecations_test.go @@ -16,7 +16,11 @@ limitations under the License. package rules // import "helm.sh/helm/v4/pkg/chart/v2/lint/rules" -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/require" +) func TestValidateNoDeprecations(t *testing.T) { deprecated := &k8sYamlStruct{ @@ -27,10 +31,9 @@ func TestValidateNoDeprecations(t *testing.T) { if err == nil { t.Fatal("Expected deprecated extension to be flagged") } - depErr := err.(deprecatedAPIError) - if depErr.Message == "" { - t.Fatalf("Expected error message to be non-blank: %v", err) - } + var depErr deprecatedAPIError + require.ErrorAs(t, err, &depErr) + require.NotEmptyf(t, depErr.Message, "Expected error message to be non-blank") if err := validateNoDeprecations(&k8sYamlStruct{ APIVersion: "v1", diff --git a/pkg/chart/v2/lint/rules/template_test.go b/pkg/chart/v2/lint/rules/template_test.go index f0eb008b3..12eec7531 100644 --- a/pkg/chart/v2/lint/rules/template_test.go +++ b/pkg/chart/v2/lint/rules/template_test.go @@ -24,6 +24,9 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/chart/v2/lint/support" @@ -235,10 +238,9 @@ func TestDeprecatedAPIFails(t *testing.T) { t.Fatalf("Expected 1 lint error, got %d", l) } - err := linter.Messages[0].Err.(deprecatedAPIError) - if err.Deprecated != "apps/v1beta1 Deployment" { - t.Errorf("Surprised to learn that %q is deprecated", err.Deprecated) - } + var depErr deprecatedAPIError + require.ErrorAs(t, linter.Messages[0].Err, &depErr) + assert.Equalf(t, "apps/v1beta1 Deployment", depErr.Deprecated, "Surprised to learn that %q is deprecated", depErr.Deprecated) } const manifest = `apiVersion: v1 diff --git a/pkg/chart/v2/util/dependencies_test.go b/pkg/chart/v2/util/dependencies_test.go index 90a8806ec..40a17ac3a 100644 --- a/pkg/chart/v2/util/dependencies_test.go +++ b/pkg/chart/v2/util/dependencies_test.go @@ -21,6 +21,8 @@ import ( "strconv" "testing" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/chart/v2/loader" @@ -251,12 +253,8 @@ func TestProcessDependencyImportValues(t *testing.T) { if err == nil { t.Error("expect nil value not found but found it") } - switch xerr := err.(type) { - case common.ErrNoValue: - // We found what we expected - default: - t.Errorf("expected an ErrNoValue but got %q instead", xerr) - } + var xerr common.ErrNoValue + require.ErrorAs(t, err, &xerr, "expected an ErrNoValue") c = loadChart(t, "testdata/subpop") if err := processDependencyImportValues(c, true); err != nil { diff --git a/pkg/cmd/plugin_test.go b/pkg/cmd/plugin_test.go index 476997110..5364423a0 100644 --- a/pkg/cmd/plugin_test.go +++ b/pkg/cmd/plugin_test.go @@ -144,16 +144,10 @@ func TestLoadCLIPlugins(t *testing.T) { // tests until this is fixed if runtime.GOOS != "windows" { if err := pluginCmd.RunE(pluginCmd, tt.args); err != nil { - if tt.code > 0 { - cerr, ok := err.(CommandError) - if !ok { - t.Errorf("Expected %s to return pluginError: got %v(%T)", tt.use, err, err) - } - if cerr.ExitCode != tt.code { - t.Errorf("Expected %s to return %d: got %d", tt.use, tt.code, cerr.ExitCode) - } - } else { - t.Errorf("Error running %s: %+v", tt.use, err) + if assert.Positive(t, tt.code, "Error running %s: %+v", tt.use, err) { + var cerr CommandError + require.ErrorAs(t, err, &cerr, "Expected %s to return pluginError: got %v(%T)", tt.use, err, err) + assert.Equalf(t, tt.code, cerr.ExitCode, "Expected %s to return %d: got %d", tt.use, tt.code, cerr.ExitCode) } } assert.Equal(t, tt.expect, out.String(), "expected output for %q", tt.use) @@ -218,16 +212,10 @@ func TestLoadPluginsWithSpace(t *testing.T) { // tests until this is fixed if runtime.GOOS != "windows" { if err := pp.RunE(pp, tt.args); err != nil { - if tt.code > 0 { - cerr, ok := err.(CommandError) - if !ok { - t.Errorf("Expected %s to return pluginError: got %v(%T)", tt.use, err, err) - } - if cerr.ExitCode != tt.code { - t.Errorf("Expected %s to return %d: got %d", tt.use, tt.code, cerr.ExitCode) - } - } else { - t.Errorf("Error running %s: %+v", tt.use, err) + if assert.Positive(t, tt.code, "Error running %s: %+v", tt.use, err) { + var cerr CommandError + require.ErrorAs(t, err, &cerr, "Expected %s to return pluginError: got %v(%T)", tt.use, err, err) + assert.Equalf(t, tt.code, cerr.ExitCode, "Expected %s to return %d: got %d", tt.use, tt.code, cerr.ExitCode) } } assert.Equal(t, tt.expect, out.String(), "expected output for %s", tt.use) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index e363fad61..1dfa88800 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -249,7 +249,7 @@ func (c *Client) getKubeClient() (kubernetes.Interface, error) { // IsReachable tests connectivity to the cluster. func (c *Client) IsReachable() error { client, err := c.getKubeClient() - if err == genericclioptions.ErrEmptyConfig { + if errors.Is(err, genericclioptions.ErrEmptyConfig) { // re-replace kubernetes ErrEmptyConfig error with a friendly error // moar workarounds for Kubernetes API breaking. return errors.New("kubernetes cluster unreachable") @@ -949,11 +949,12 @@ func (c *Client) Delete(resources ResourceList, policy metav1.DeletionPropagatio func isIncompatibleServerError(err error) bool { // 415: Unsupported media type means we're talking to a server which doesn't // support server-side apply. - if _, ok := err.(*apierrors.StatusError); !ok { + var sErr *apierrors.StatusError + if !errors.As(err, &sErr) { // Non-StatusError means the error isn't because the server is incompatible. return false } - return err.(*apierrors.StatusError).Status().Code == http.StatusUnsupportedMediaType + return sErr.Status().Code == http.StatusUnsupportedMediaType } // isServerSideRetryable checks if an error encountered during server-side apply diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go index 3f8e654f9..79b33a970 100644 --- a/pkg/provenance/sign_test.go +++ b/pkg/provenance/sign_test.go @@ -385,11 +385,9 @@ func TestVerify(t *testing.T) { _, err = signer.Verify(archiveData, tamperedSigData, filepath.Base(testChartfile)) require.Errorf(t, err, "Expected %s to fail.", testTamperedSigBlock) - switch err.(type) { - case pgperrors.SignatureError: - t.Logf("Tampered sig block error: %s (%T)", err, err) - default: - t.Errorf("Expected invalid signature error, got %q (%T)", err, err) + var sErr pgperrors.SignatureError + if assert.ErrorAs(t, err, &sErr, "Expected invalid signature error, got %q (%T)", err, err) { + t.Logf("Tampered sig block error: %s (%T)", sErr, sErr) } } diff --git a/pkg/registry/transport.go b/pkg/registry/transport.go index beec43cdd..fa0103ac0 100644 --- a/pkg/registry/transport.go +++ b/pkg/registry/transport.go @@ -18,6 +18,7 @@ package registry import ( "bytes" + "errors" "fmt" "io" "log/slog" @@ -126,7 +127,7 @@ func logResponseBody(resp *http.Response) string { Closer: body, } // read the body up to limit+1 to check if the body exceeds the limit - if _, err := io.CopyN(buf, body, payloadSizeLimit+1); err != nil && err != io.EOF { + if _, err := io.CopyN(buf, body, payloadSizeLimit+1); err != nil && !errors.Is(err, io.EOF) { return fmt.Sprintf(" Error reading response body: %v", err) } diff --git a/pkg/repo/v1/index.go b/pkg/repo/v1/index.go index 839a4cca9..1c1497c5e 100644 --- a/pkg/repo/v1/index.go +++ b/pkg/repo/v1/index.go @@ -421,8 +421,8 @@ func jsonOrYamlUnmarshal(b []byte, i any) error { // And repository indexes may be generated by older/non-compliant software, which doesn't // conform to all validations. func ignoreSkippableChartValidationError(err error) error { - verr, ok := err.(chart.ValidationError) - if !ok { + var verr chart.ValidationError + if !errors.As(err, &verr) { return err } diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index d74e46986..be3b1e71b 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -238,16 +238,16 @@ func (t *parser) key(data map[string]any, nestedNameLevel int) (reterr error) { // End of key. Consume =, Get value. // FIXME: Get value list first vl, e := t.valList() - switch e { - case nil: + switch { + case e == nil: set(data, string(k), vl) return nil - case io.EOF: + case errors.Is(e, io.EOF): set(data, string(k), "") return e - case ErrNotList: + case errors.Is(e, ErrNotList): rs, e := t.val() - if e != nil && e != io.EOF { + if e != nil && !errors.Is(e, io.EOF) { return e } v, e := t.reader(rs) @@ -370,14 +370,14 @@ func (t *parser) listItem(list []any, i, nestedNameLevel int) ([]any, error) { return list, err } vl, e := t.valList() - switch e { - case nil: + switch { + case e == nil: return setIndex(list, i, vl) - case io.EOF: + case errors.Is(e, io.EOF): return setIndex(list, i, "") - case ErrNotList: + case errors.Is(e, ErrNotList): rs, e := t.val() - if e != nil && e != io.EOF { + if e != nil && !errors.Is(e, io.EOF) { return list, e } v, e := t.reader(rs) @@ -476,7 +476,7 @@ func (t *parser) valList() ([]any, error) { for { switch rs, last, err := runesUntil(t.sc, stop); { case err != nil: - if err == io.EOF { + if errors.Is(err, io.EOF) { err = errors.New("list must terminate with '}'") } return list, err From c2fda5ac5835cd1eca44a29047c18edcde66e00f Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sat, 18 Jul 2026 05:33:34 -0600 Subject: [PATCH 446/571] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Terry Howe --- pkg/cli/environment_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go index 2d7851a4f..fc71038f8 100644 --- a/pkg/cli/environment_test.go +++ b/pkg/cli/environment_test.go @@ -242,9 +242,7 @@ users: user: token: test-token ` - if err := os.WriteFile(kubeconfigPath, []byte(kubeconfig), 0o600); err != nil { - t.Fatalf("failed to create test kubeconfig: %v", err) - } + require.NoError(t, os.WriteFile(kubeconfigPath, []byte(kubeconfig), 0o600), "failed to create test kubeconfig") t.Setenv("KUBECONFIG", kubeconfigPath) settings := New() From 82be04d7f2881f59b3a85483eb248417e1d18fc3 Mon Sep 17 00:00:00 2001 From: Abhay Chaurasiya <44005156+abhay1999@users.noreply.github.com> Date: Sat, 18 Jul 2026 17:12:20 +0530 Subject: [PATCH 447/571] remove legacy import comments from remaining packages (#31933) Pre-Go-modules import path comments (e.g. `// import "helm.sh/helm/v4/..."`) are obsolete since Go 1.11 where go.mod is the authoritative module path. These stale comments cause issues with downstream tooling such as Kythe. Removes the legacy import comments from 64 files across 20 packages: - cmd/helm - internal/chart/v3/lint and sub-packages - internal/plugin and sub-packages - internal/release/v2/util - pkg/chart/v2/lint and sub-packages - pkg/cmd, pkg/engine, pkg/ignore, pkg/provenance - pkg/registry - pkg/release/v1/util - pkg/repo/v1 - pkg/storage and pkg/storage/driver Follows up on #31931 and #31932 which addressed pkg/kube. Fixes #31846 Signed-off-by: abhay1999 Signed-off-by: abhay1999 --- cmd/helm/helm.go | 2 +- internal/chart/v3/lint/lint.go | 2 +- internal/chart/v3/lint/rules/chartfile.go | 2 +- internal/chart/v3/lint/rules/dependencies.go | 2 +- internal/chart/v3/lint/rules/deprecations.go | 2 +- internal/chart/v3/lint/rules/deprecations_test.go | 2 +- internal/chart/v3/lint/support/doc.go | 2 +- internal/plugin/cache/cache.go | 2 +- internal/plugin/installer/base.go | 2 +- internal/plugin/installer/base_test.go | 2 +- internal/plugin/installer/doc.go | 2 +- internal/plugin/installer/extractor.go | 2 +- internal/plugin/installer/http_installer.go | 2 +- internal/plugin/installer/http_installer_test.go | 2 +- internal/plugin/installer/local_installer.go | 2 +- internal/plugin/installer/local_installer_test.go | 2 +- internal/plugin/installer/oci_installer_test.go | 2 +- internal/plugin/installer/vcs_installer.go | 2 +- internal/plugin/installer/vcs_installer_test.go | 2 +- internal/plugin/plugin.go | 2 +- internal/plugin/runtime_subprocess_hooks.go | 2 +- internal/release/v2/util/filter.go | 2 +- internal/release/v2/util/filter_test.go | 2 +- internal/release/v2/util/kind_sorter.go | 2 +- internal/release/v2/util/kind_sorter_test.go | 2 +- internal/release/v2/util/manifest.go | 2 +- internal/release/v2/util/manifest_sorter.go | 2 +- internal/release/v2/util/manifest_sorter_test.go | 2 +- internal/release/v2/util/manifest_test.go | 2 +- internal/release/v2/util/sorter.go | 2 +- internal/release/v2/util/sorter_test.go | 2 +- pkg/chart/v2/lint/lint.go | 2 +- pkg/chart/v2/lint/rules/chartfile.go | 2 +- pkg/chart/v2/lint/rules/dependencies.go | 2 +- pkg/chart/v2/lint/rules/deprecations.go | 2 +- pkg/chart/v2/lint/rules/deprecations_test.go | 2 +- pkg/chart/v2/lint/support/doc.go | 2 +- pkg/cmd/root.go | 2 +- pkg/engine/doc.go | 2 +- pkg/ignore/doc.go | 2 +- pkg/provenance/doc.go | 2 +- pkg/registry/chart.go | 2 +- pkg/registry/chart_test.go | 2 +- pkg/registry/client.go | 2 +- pkg/registry/constants.go | 2 +- pkg/registry/tag.go | 2 +- pkg/release/v1/util/filter.go | 2 +- pkg/release/v1/util/filter_test.go | 2 +- pkg/release/v1/util/manifest_test.go | 2 +- pkg/release/v1/util/sorter.go | 2 +- pkg/release/v1/util/sorter_test.go | 2 +- pkg/repo/v1/chartrepo.go | 2 +- pkg/repo/v1/repo.go | 2 +- pkg/storage/driver/cfgmaps.go | 2 +- pkg/storage/driver/driver.go | 2 +- pkg/storage/driver/labels_test.go | 2 +- pkg/storage/driver/mock_test.go | 2 +- pkg/storage/driver/records.go | 2 +- pkg/storage/driver/records_test.go | 2 +- pkg/storage/driver/secrets.go | 2 +- pkg/storage/driver/sql.go | 2 +- pkg/storage/driver/util.go | 2 +- pkg/storage/storage.go | 2 +- pkg/storage/storage_test.go | 2 +- 64 files changed, 64 insertions(+), 64 deletions(-) diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 0c4f697b6..60df8a308 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main // import "helm.sh/helm/v4/cmd/helm" +package main import ( "errors" diff --git a/internal/chart/v3/lint/lint.go b/internal/chart/v3/lint/lint.go index ea8ca3d60..193f0f796 100644 --- a/internal/chart/v3/lint/lint.go +++ b/internal/chart/v3/lint/lint.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package lint // import "helm.sh/helm/v4/internal/chart/v3/lint" +package lint import ( "path/filepath" diff --git a/internal/chart/v3/lint/rules/chartfile.go b/internal/chart/v3/lint/rules/chartfile.go index db7ac61dc..c76654d53 100644 --- a/internal/chart/v3/lint/rules/chartfile.go +++ b/internal/chart/v3/lint/rules/chartfile.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules" +package rules import ( "errors" diff --git a/internal/chart/v3/lint/rules/dependencies.go b/internal/chart/v3/lint/rules/dependencies.go index f45153728..2f558aaf0 100644 --- a/internal/chart/v3/lint/rules/dependencies.go +++ b/internal/chart/v3/lint/rules/dependencies.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules" +package rules import ( "fmt" diff --git a/internal/chart/v3/lint/rules/deprecations.go b/internal/chart/v3/lint/rules/deprecations.go index 9c73268af..85dddd114 100644 --- a/internal/chart/v3/lint/rules/deprecations.go +++ b/internal/chart/v3/lint/rules/deprecations.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules" +package rules import ( "fmt" diff --git a/internal/chart/v3/lint/rules/deprecations_test.go b/internal/chart/v3/lint/rules/deprecations_test.go index 55c454118..920fdc31f 100644 --- a/internal/chart/v3/lint/rules/deprecations_test.go +++ b/internal/chart/v3/lint/rules/deprecations_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package rules // import "helm.sh/helm/v4/internal/chart/v3/lint/rules" +package rules import ( "testing" diff --git a/internal/chart/v3/lint/support/doc.go b/internal/chart/v3/lint/support/doc.go index 2d54a9b7d..80b1d2169 100644 --- a/internal/chart/v3/lint/support/doc.go +++ b/internal/chart/v3/lint/support/doc.go @@ -20,4 +20,4 @@ Package support contains tools for linting charts. Linting is the process of testing charts for errors or warnings regarding formatting, compilation, or standards compliance. */ -package support // import "helm.sh/helm/v4/internal/chart/v3/lint/support" +package support diff --git a/internal/plugin/cache/cache.go b/internal/plugin/cache/cache.go index f3b737477..1613d4d76 100644 --- a/internal/plugin/cache/cache.go +++ b/internal/plugin/cache/cache.go @@ -14,7 +14,7 @@ limitations under the License. */ // Package cache provides a key generator for vcs urls. -package cache // import "helm.sh/helm/v4/internal/plugin/cache" +package cache import ( "net/url" diff --git a/internal/plugin/installer/base.go b/internal/plugin/installer/base.go index c21a245a8..54d25287a 100644 --- a/internal/plugin/installer/base.go +++ b/internal/plugin/installer/base.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v4/internal/plugin/installer" +package installer import ( "path/filepath" diff --git a/internal/plugin/installer/base_test.go b/internal/plugin/installer/base_test.go index 6df8ec8a1..b7902e699 100644 --- a/internal/plugin/installer/base_test.go +++ b/internal/plugin/installer/base_test.go @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v4/internal/plugin/installer" +package installer import ( "testing" diff --git a/internal/plugin/installer/doc.go b/internal/plugin/installer/doc.go index a4cf384bf..a9d9555c9 100644 --- a/internal/plugin/installer/doc.go +++ b/internal/plugin/installer/doc.go @@ -14,4 +14,4 @@ limitations under the License. */ // Package installer provides an interface for installing Helm plugins. -package installer // import "helm.sh/helm/v4/internal/plugin/installer" +package installer diff --git a/internal/plugin/installer/extractor.go b/internal/plugin/installer/extractor.go index 85d695bd2..6cd45e2d8 100644 --- a/internal/plugin/installer/extractor.go +++ b/internal/plugin/installer/extractor.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v4/internal/plugin/installer" +package installer import ( "archive/tar" diff --git a/internal/plugin/installer/http_installer.go b/internal/plugin/installer/http_installer.go index 92d3d686e..7be326de2 100644 --- a/internal/plugin/installer/http_installer.go +++ b/internal/plugin/installer/http_installer.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v4/internal/plugin/installer" +package installer import ( "bytes" diff --git a/internal/plugin/installer/http_installer_test.go b/internal/plugin/installer/http_installer_test.go index cbd868f3a..3235e9796 100644 --- a/internal/plugin/installer/http_installer_test.go +++ b/internal/plugin/installer/http_installer_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v4/internal/plugin/installer" +package installer import ( "archive/tar" diff --git a/internal/plugin/installer/local_installer.go b/internal/plugin/installer/local_installer.go index a7a6caa14..59918401b 100644 --- a/internal/plugin/installer/local_installer.go +++ b/internal/plugin/installer/local_installer.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v4/internal/plugin/installer" +package installer import ( "bytes" diff --git a/internal/plugin/installer/local_installer_test.go b/internal/plugin/installer/local_installer_test.go index 02d4d7bff..f46fcb44b 100644 --- a/internal/plugin/installer/local_installer_test.go +++ b/internal/plugin/installer/local_installer_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v4/internal/plugin/installer" +package installer import ( "archive/tar" diff --git a/internal/plugin/installer/oci_installer_test.go b/internal/plugin/installer/oci_installer_test.go index c3bebdb52..48eccf6e1 100644 --- a/internal/plugin/installer/oci_installer_test.go +++ b/internal/plugin/installer/oci_installer_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v4/internal/plugin/installer" +package installer import ( "archive/tar" diff --git a/internal/plugin/installer/vcs_installer.go b/internal/plugin/installer/vcs_installer.go index 3601ec7a8..13aba74dc 100644 --- a/internal/plugin/installer/vcs_installer.go +++ b/internal/plugin/installer/vcs_installer.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v4/internal/plugin/installer" +package installer import ( "errors" diff --git a/internal/plugin/installer/vcs_installer_test.go b/internal/plugin/installer/vcs_installer_test.go index 401f2b921..7e44cc957 100644 --- a/internal/plugin/installer/vcs_installer_test.go +++ b/internal/plugin/installer/vcs_installer_test.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package installer // import "helm.sh/helm/v4/internal/plugin/installer" +package installer import ( "fmt" diff --git a/internal/plugin/plugin.go b/internal/plugin/plugin.go index 789e583ae..ad2dd7922 100644 --- a/internal/plugin/plugin.go +++ b/internal/plugin/plugin.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package plugin // import "helm.sh/helm/v4/internal/plugin" +package plugin import ( "context" diff --git a/internal/plugin/runtime_subprocess_hooks.go b/internal/plugin/runtime_subprocess_hooks.go index 7b4ff5a38..8d5f3cba6 100644 --- a/internal/plugin/runtime_subprocess_hooks.go +++ b/internal/plugin/runtime_subprocess_hooks.go @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package plugin // import "helm.sh/helm/v4/internal/plugin" +package plugin // Types of hooks const ( diff --git a/internal/release/v2/util/filter.go b/internal/release/v2/util/filter.go index bc568cbdc..0daf06014 100644 --- a/internal/release/v2/util/filter.go +++ b/internal/release/v2/util/filter.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util // import "helm.sh/helm/v4/internal/release/v2/util" +package util import ( v2 "helm.sh/helm/v4/internal/release/v2" diff --git a/internal/release/v2/util/filter_test.go b/internal/release/v2/util/filter_test.go index 8c7363b87..542510ba3 100644 --- a/internal/release/v2/util/filter_test.go +++ b/internal/release/v2/util/filter_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util // import "helm.sh/helm/v4/internal/release/v2/util" +package util import ( "testing" diff --git a/internal/release/v2/util/kind_sorter.go b/internal/release/v2/util/kind_sorter.go index dba35b6d2..950e836fa 100644 --- a/internal/release/v2/util/kind_sorter.go +++ b/internal/release/v2/util/kind_sorter.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util // import "helm.sh/helm/v4/internal/release/v2/util" +package util import ( "sort" diff --git a/internal/release/v2/util/kind_sorter_test.go b/internal/release/v2/util/kind_sorter_test.go index dba19e287..06885aa93 100644 --- a/internal/release/v2/util/kind_sorter_test.go +++ b/internal/release/v2/util/kind_sorter_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util // import "helm.sh/helm/v4/internal/release/v2/util" +package util import ( "bytes" diff --git a/internal/release/v2/util/manifest.go b/internal/release/v2/util/manifest.go index d3296984e..37b9dfbd3 100644 --- a/internal/release/v2/util/manifest.go +++ b/internal/release/v2/util/manifest.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util // import "helm.sh/helm/v4/internal/release/v2/util" +package util import ( "fmt" diff --git a/internal/release/v2/util/manifest_sorter.go b/internal/release/v2/util/manifest_sorter.go index f269dda6d..3507deed5 100644 --- a/internal/release/v2/util/manifest_sorter.go +++ b/internal/release/v2/util/manifest_sorter.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util // import "helm.sh/helm/v4/internal/release/v2/util" +package util import ( "fmt" diff --git a/internal/release/v2/util/manifest_sorter_test.go b/internal/release/v2/util/manifest_sorter_test.go index eac9c9f36..deb9426ce 100644 --- a/internal/release/v2/util/manifest_sorter_test.go +++ b/internal/release/v2/util/manifest_sorter_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util // import "helm.sh/helm/v4/internal/release/v2/util" +package util import ( "testing" diff --git a/internal/release/v2/util/manifest_test.go b/internal/release/v2/util/manifest_test.go index 1806368af..e76905a63 100644 --- a/internal/release/v2/util/manifest_test.go +++ b/internal/release/v2/util/manifest_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util // import "helm.sh/helm/v4/internal/release/v2/util" +package util import ( "testing" diff --git a/internal/release/v2/util/sorter.go b/internal/release/v2/util/sorter.go index 5f13569c1..66b51d873 100644 --- a/internal/release/v2/util/sorter.go +++ b/internal/release/v2/util/sorter.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util // import "helm.sh/helm/v4/internal/release/v2/util" +package util import ( "sort" diff --git a/internal/release/v2/util/sorter_test.go b/internal/release/v2/util/sorter_test.go index 82a53ae66..e151f0790 100644 --- a/internal/release/v2/util/sorter_test.go +++ b/internal/release/v2/util/sorter_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util // import "helm.sh/helm/v4/internal/release/v2/util" +package util import ( "testing" diff --git a/pkg/chart/v2/lint/lint.go b/pkg/chart/v2/lint/lint.go index 23a93c551..204c15861 100644 --- a/pkg/chart/v2/lint/lint.go +++ b/pkg/chart/v2/lint/lint.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package lint // import "helm.sh/helm/v4/pkg/chart/v2/lint" +package lint import ( "path/filepath" diff --git a/pkg/chart/v2/lint/rules/chartfile.go b/pkg/chart/v2/lint/rules/chartfile.go index 08508f46e..f867daa21 100644 --- a/pkg/chart/v2/lint/rules/chartfile.go +++ b/pkg/chart/v2/lint/rules/chartfile.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package rules // import "helm.sh/helm/v4/pkg/chart/v2/lint/rules" +package rules import ( "errors" diff --git a/pkg/chart/v2/lint/rules/dependencies.go b/pkg/chart/v2/lint/rules/dependencies.go index d944a016d..616984c08 100644 --- a/pkg/chart/v2/lint/rules/dependencies.go +++ b/pkg/chart/v2/lint/rules/dependencies.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package rules // import "helm.sh/helm/v4/pkg/chart/v2/lint/rules" +package rules import ( "fmt" diff --git a/pkg/chart/v2/lint/rules/deprecations.go b/pkg/chart/v2/lint/rules/deprecations.go index 76626659b..2ed682891 100644 --- a/pkg/chart/v2/lint/rules/deprecations.go +++ b/pkg/chart/v2/lint/rules/deprecations.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package rules // import "helm.sh/helm/v4/pkg/chart/v2/lint/rules" +package rules import ( "fmt" diff --git a/pkg/chart/v2/lint/rules/deprecations_test.go b/pkg/chart/v2/lint/rules/deprecations_test.go index fdeb47e91..0aa983678 100644 --- a/pkg/chart/v2/lint/rules/deprecations_test.go +++ b/pkg/chart/v2/lint/rules/deprecations_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package rules // import "helm.sh/helm/v4/pkg/chart/v2/lint/rules" +package rules import ( "testing" diff --git a/pkg/chart/v2/lint/support/doc.go b/pkg/chart/v2/lint/support/doc.go index 7e050b8c2..80b1d2169 100644 --- a/pkg/chart/v2/lint/support/doc.go +++ b/pkg/chart/v2/lint/support/doc.go @@ -20,4 +20,4 @@ Package support contains tools for linting charts. Linting is the process of testing charts for errors or warnings regarding formatting, compilation, or standards compliance. */ -package support // import "helm.sh/helm/v4/pkg/chart/v2/lint/support" +package support diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 28798de5c..410a9162c 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cmd // import "helm.sh/helm/v4/pkg/cmd" +package cmd import ( "context" diff --git a/pkg/engine/doc.go b/pkg/engine/doc.go index e764a829a..9bb1059f2 100644 --- a/pkg/engine/doc.go +++ b/pkg/engine/doc.go @@ -21,4 +21,4 @@ When Helm renders templates it does so with additional functions and different modes (e.g., strict, lint mode). This package handles the helm specific implementation. */ -package engine // import "helm.sh/helm/v4/pkg/engine" +package engine diff --git a/pkg/ignore/doc.go b/pkg/ignore/doc.go index a66066eb2..bba658d41 100644 --- a/pkg/ignore/doc.go +++ b/pkg/ignore/doc.go @@ -65,4 +65,4 @@ Notable differences from .gitignore: - The evaluation of escape sequences has not been tested for compatibility - There is no support for '\!' as a special leading sequence. */ -package ignore // import "helm.sh/helm/v4/pkg/ignore" +package ignore diff --git a/pkg/provenance/doc.go b/pkg/provenance/doc.go index dd14568d9..bd60bc593 100644 --- a/pkg/provenance/doc.go +++ b/pkg/provenance/doc.go @@ -35,4 +35,4 @@ and using `gpg --verify`, `keybase pgp verify`, or similar: gpg: Signature made Mon Jul 25 17:23:44 2016 MDT using RSA key ID 1FC18762 gpg: Good signature from "Helm Testing (This key should only be used for testing. DO NOT TRUST.) " [ultimate] */ -package provenance // import "helm.sh/helm/v4/pkg/provenance" +package provenance diff --git a/pkg/registry/chart.go b/pkg/registry/chart.go index 7c41785c5..b8b0d88b6 100644 --- a/pkg/registry/chart.go +++ b/pkg/registry/chart.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package registry // import "helm.sh/helm/v4/pkg/registry" +package registry import ( "bytes" diff --git a/pkg/registry/chart_test.go b/pkg/registry/chart_test.go index e27c3d619..c80544b5b 100644 --- a/pkg/registry/chart_test.go +++ b/pkg/registry/chart_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package registry // import "helm.sh/helm/v4/pkg/registry" +package registry import ( "testing" diff --git a/pkg/registry/client.go b/pkg/registry/client.go index cba346dea..d1a744f4b 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package registry // import "helm.sh/helm/v4/pkg/registry" +package registry import ( "context" diff --git a/pkg/registry/constants.go b/pkg/registry/constants.go index c455cf314..df31a18c4 100644 --- a/pkg/registry/constants.go +++ b/pkg/registry/constants.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package registry // import "helm.sh/helm/v4/pkg/registry" +package registry const ( // OCIScheme is the URL scheme for OCI-based requests diff --git a/pkg/registry/tag.go b/pkg/registry/tag.go index bfb4b1ef6..7e15d0dd9 100644 --- a/pkg/registry/tag.go +++ b/pkg/registry/tag.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package registry // import "helm.sh/helm/v4/pkg/registry" +package registry import ( "fmt" diff --git a/pkg/release/v1/util/filter.go b/pkg/release/v1/util/filter.go index dc60195cf..88ac995d7 100644 --- a/pkg/release/v1/util/filter.go +++ b/pkg/release/v1/util/filter.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util // import "helm.sh/helm/v4/pkg/release/v1/util" +package util import ( "helm.sh/helm/v4/pkg/release/common" diff --git a/pkg/release/v1/util/filter_test.go b/pkg/release/v1/util/filter_test.go index d6bda8f84..57d7e6801 100644 --- a/pkg/release/v1/util/filter_test.go +++ b/pkg/release/v1/util/filter_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util // import "helm.sh/helm/v4/pkg/release/v1/util" +package util import ( "testing" diff --git a/pkg/release/v1/util/manifest_test.go b/pkg/release/v1/util/manifest_test.go index 773723c86..e76905a63 100644 --- a/pkg/release/v1/util/manifest_test.go +++ b/pkg/release/v1/util/manifest_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util // import "helm.sh/helm/v4/pkg/release/v1/util" +package util import ( "testing" diff --git a/pkg/release/v1/util/sorter.go b/pkg/release/v1/util/sorter.go index 47506fbf2..e15b87593 100644 --- a/pkg/release/v1/util/sorter.go +++ b/pkg/release/v1/util/sorter.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util // import "helm.sh/helm/v4/pkg/release/v1/util" +package util import ( "sort" diff --git a/pkg/release/v1/util/sorter_test.go b/pkg/release/v1/util/sorter_test.go index 0450b09e6..1a3bb3e6a 100644 --- a/pkg/release/v1/util/sorter_test.go +++ b/pkg/release/v1/util/sorter_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util // import "helm.sh/helm/v4/pkg/release/v1/util" +package util import ( "testing" diff --git a/pkg/repo/v1/chartrepo.go b/pkg/repo/v1/chartrepo.go index c7c4a3e7b..42ed52f75 100644 --- a/pkg/repo/v1/chartrepo.go +++ b/pkg/repo/v1/chartrepo.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package repo // import "helm.sh/helm/v4/pkg/repo/v1" +package repo import ( "bytes" diff --git a/pkg/repo/v1/repo.go b/pkg/repo/v1/repo.go index 199b56ed8..be241a0ab 100644 --- a/pkg/repo/v1/repo.go +++ b/pkg/repo/v1/repo.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package repo // import "helm.sh/helm/v4/pkg/repo/v1" +package repo import ( "fmt" diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go index 2636755a9..f71ce44f1 100644 --- a/pkg/storage/driver/cfgmaps.go +++ b/pkg/storage/driver/cfgmaps.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v4/pkg/storage/driver" +package driver import ( "context" diff --git a/pkg/storage/driver/driver.go b/pkg/storage/driver/driver.go index 6efd1dbaa..a7d194d69 100644 --- a/pkg/storage/driver/driver.go +++ b/pkg/storage/driver/driver.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v4/pkg/storage/driver" +package driver import ( "errors" diff --git a/pkg/storage/driver/labels_test.go b/pkg/storage/driver/labels_test.go index 81e561c15..848ae8861 100644 --- a/pkg/storage/driver/labels_test.go +++ b/pkg/storage/driver/labels_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v4/pkg/storage/driver" +package driver import ( "testing" diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go index e62b02f43..ab7b261cf 100644 --- a/pkg/storage/driver/mock_test.go +++ b/pkg/storage/driver/mock_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v4/pkg/storage/driver" +package driver import ( "context" diff --git a/pkg/storage/driver/records.go b/pkg/storage/driver/records.go index 6b4efef3a..f78b76b8b 100644 --- a/pkg/storage/driver/records.go +++ b/pkg/storage/driver/records.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v4/pkg/storage/driver" +package driver import ( "sort" diff --git a/pkg/storage/driver/records_test.go b/pkg/storage/driver/records_test.go index 24e4ccb4e..cd1064342 100644 --- a/pkg/storage/driver/records_test.go +++ b/pkg/storage/driver/records_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v4/pkg/storage/driver" +package driver import ( "reflect" diff --git a/pkg/storage/driver/secrets.go b/pkg/storage/driver/secrets.go index 5e12684df..a1f3e94fc 100644 --- a/pkg/storage/driver/secrets.go +++ b/pkg/storage/driver/secrets.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v4/pkg/storage/driver" +package driver import ( "context" diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go index 88d24123d..85e6cbd3f 100644 --- a/pkg/storage/driver/sql.go +++ b/pkg/storage/driver/sql.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v4/pkg/storage/driver" +package driver import ( "fmt" diff --git a/pkg/storage/driver/util.go b/pkg/storage/driver/util.go index 4bc48db23..08c38585c 100644 --- a/pkg/storage/driver/util.go +++ b/pkg/storage/driver/util.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package driver // import "helm.sh/helm/v4/pkg/storage/driver" +package driver import ( "bytes" diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index d5d2ea317..1d7fb9103 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package storage // import "helm.sh/helm/v4/pkg/storage" +package storage import ( "errors" diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index 32f926e08..8ecee447f 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package storage // import "helm.sh/helm/v4/pkg/storage" +package storage import ( "context" From 3bcf965f997499c477436515e6ccf5d51ab82e5e Mon Sep 17 00:00:00 2001 From: Zhaofeng Miao <522856232@qq.com> Date: Sat, 18 Jul 2026 21:10:36 +0800 Subject: [PATCH 448/571] perf: enable concurrent status computation to prevent multi-minute delays (#32043) * fix: enable concurrent status computation to prevent multi-minute delays Set StatusComputeWorkers=8 on DefaultStatusWatcher for Wait, WaitWithJobs, and WatchUntilReady. This opts in to the async status computation added in fluxcd/cli-utils#20, preventing the informer notification pipeline from being blocked by slow API calls when many resources are updated simultaneously. Without this, status computation for resources like Deployments (which require additional LIST ReplicaSets/Pods calls) runs serially inside the informer, causing growing delays of 1-3+ minutes when upgrading many resources at once (e.g., ~20 Deployments via Helm). Signed-off-by: maplemiao * refactor(kube): expose StatusComputeWorkers as a WaitOption Replace the package-level DefaultStatusComputeWorkers variable with a WithStatusComputeWorkers WaitOption threaded through waitOptions into the statusWaiter. This removes global mutable state from pkg/kube and lets callers opt in explicitly. SDK consumers (e.g. helm-controller) inherit the zero value, which preserves the upstream cli-utils synchronous behavior and avoids an unexpected fan-out of status-compute goroutines when many releases reconcile concurrently. The Helm CLI continues to enable 8 workers by default via a shared pkg/cmd/flags.go helper, so install/upgrade/ rollback/uninstall/test users still get the fix for multi-minute informer stalls out of the box. Signed-off-by: maplemiao * refactor(kube): clamp negative StatusComputeWorkers values to zero Address review feedback: when a caller passes a negative value to WithStatusComputeWorkers, coerce it to zero rather than propagating it to the underlying cli-utils watcher, where the behavior is undefined. Zero is the safe default and matches the SDK opt-in contract. Signed-off-by: maplemiao --------- Signed-off-by: maplemiao Signed-off-by: Maple Miao Co-authored-by: Cursor --- pkg/cmd/flags.go | 20 ++++++++++++++++++++ pkg/cmd/install.go | 1 + pkg/cmd/release_testing.go | 1 + pkg/cmd/rollback.go | 1 + pkg/cmd/uninstall.go | 1 + pkg/cmd/upgrade.go | 2 ++ pkg/kube/client.go | 17 +++++++++-------- pkg/kube/options.go | 35 +++++++++++++++++++++++++++++------ pkg/kube/statuswait.go | 20 ++++++++++++-------- pkg/kube/statuswait_test.go | 22 ++++++++++++++++++++++ 10 files changed, 98 insertions(+), 22 deletions(-) diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index e7db3dde2..e59487044 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -65,6 +65,26 @@ func AddWaitFlag(cmd *cobra.Command, wait *kube.WaitStrategy) { cmd.Flags().Lookup("wait").NoOptDefVal = string(kube.StatusWatcherStrategy) } +// cliDefaultStatusComputeWorkers is the number of concurrent status-compute +// workers the Helm CLI enables by default. This prevents the informer +// notification pipeline from being blocked by slow API calls (e.g. LIST +// ReplicaSets/Pods for Deployments) when many resources are updated +// simultaneously. See https://github.com/fluxcd/cli-utils/pull/20. +// +// SDK consumers (e.g. helm-controller) inherit the zero value and can opt in +// via kube.WithStatusComputeWorkers when they want the same behavior. +const cliDefaultStatusComputeWorkers = 8 + +// defaultCLIWaitOptions returns the set of WaitOptions the Helm CLI applies +// by default to every wait-enabled command. Keeping these in one place keeps +// behavior consistent across install/upgrade/rollback/uninstall and makes the +// CLI-vs-SDK default asymmetry explicit. +func defaultCLIWaitOptions() []kube.WaitOption { + return []kube.WaitOption{ + kube.WithStatusComputeWorkers(cliDefaultStatusComputeWorkers), + } +} + type waitValue kube.WaitStrategy func newWaitValue(defaultValue kube.WaitStrategy, ws *kube.WaitStrategy) *waitValue { diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index b935ed4d0..b6cee5843 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -131,6 +131,7 @@ charts in a repository, use 'helm search'. func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewInstall(cfg) + client.WaitOptions = append(client.WaitOptions, defaultCLIWaitOptions()...) valueOpts := &values.Options{} var outfmt output.Format diff --git a/pkg/cmd/release_testing.go b/pkg/cmd/release_testing.go index 5a6159e7d..6be83c966 100644 --- a/pkg/cmd/release_testing.go +++ b/pkg/cmd/release_testing.go @@ -40,6 +40,7 @@ The tests to be run are defined in the chart that was installed. func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewReleaseTesting(cfg) + client.WaitOptions = append(client.WaitOptions, defaultCLIWaitOptions()...) outfmt := output.Table var outputLogs bool var filter []string diff --git a/pkg/cmd/rollback.go b/pkg/cmd/rollback.go index 01d8b1866..b716dae10 100644 --- a/pkg/cmd/rollback.go +++ b/pkg/cmd/rollback.go @@ -40,6 +40,7 @@ To see revision numbers, run 'helm history RELEASE'. func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewRollback(cfg) + client.WaitOptions = append(client.WaitOptions, defaultCLIWaitOptions()...) cmd := &cobra.Command{ Use: "rollback [REVISION]", diff --git a/pkg/cmd/uninstall.go b/pkg/cmd/uninstall.go index bfecd4741..315c29a8d 100644 --- a/pkg/cmd/uninstall.go +++ b/pkg/cmd/uninstall.go @@ -42,6 +42,7 @@ are fully deleted before the command returns. func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewUninstall(cfg) + client.WaitOptions = append(client.WaitOptions, defaultCLIWaitOptions()...) cmd := &cobra.Command{ Use: "uninstall RELEASE_NAME [...]", diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index c038a2149..d0d0166ba 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -84,6 +84,7 @@ which can contain sensitive values. To hide Kubernetes Secrets use the func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewUpgrade(cfg) + client.WaitOptions = append(client.WaitOptions, defaultCLIWaitOptions()...) valueOpts := &values.Options{} var outfmt output.Format var createNamespace bool @@ -139,6 +140,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { instClient.SkipCRDs = client.SkipCRDs instClient.Timeout = client.Timeout instClient.WaitStrategy = client.WaitStrategy + instClient.WaitOptions = client.WaitOptions instClient.WaitForJobs = client.WaitForJobs instClient.Devel = client.Devel instClient.Namespace = client.Namespace diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 1dfa88800..bb26fbcc9 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -167,14 +167,15 @@ func (c *Client) newStatusWatcher(opts ...WaitOption) (*statusWaiter, error) { waitContext = c.WaitContext } sw := &statusWaiter{ - restMapper: restMapper, - client: dynamicClient, - ctx: waitContext, - watchUntilReadyCtx: o.watchUntilReadyCtx, - waitCtx: o.waitCtx, - waitWithJobsCtx: o.waitWithJobsCtx, - waitForDeleteCtx: o.waitForDeleteCtx, - readers: o.statusReaders, + restMapper: restMapper, + client: dynamicClient, + ctx: waitContext, + watchUntilReadyCtx: o.watchUntilReadyCtx, + waitCtx: o.waitCtx, + waitWithJobsCtx: o.waitWithJobsCtx, + waitForDeleteCtx: o.waitForDeleteCtx, + readers: o.statusReaders, + statusComputeWorkers: o.statusComputeWorkers, } sw.SetLogger(c.Logger().Handler()) return sw, nil diff --git a/pkg/kube/options.go b/pkg/kube/options.go index 3326c284b..7853cb63a 100644 --- a/pkg/kube/options.go +++ b/pkg/kube/options.go @@ -72,11 +72,34 @@ func WithKStatusReaders(readers ...engine.StatusReader) WaitOption { } } +// WithStatusComputeWorkers sets the number of concurrent goroutines used to +// compute object status per informer. This prevents the informer notification +// pipeline from being blocked by slow API calls (e.g., LIST ReplicaSets/Pods +// for Deployments) when many resources are updated simultaneously. +// +// A value of 0 (the default) keeps the underlying cli-utils behavior, where +// status is computed synchronously on the informer goroutine. Negative values +// are clamped to 0 so callers cannot propagate invalid counts to the +// underlying watcher. SDK consumers (for example helm-controller) inherit +// this conservative default and can opt in explicitly. The Helm CLI passes +// a non-zero value so that `helm install/upgrade/rollback` users get the +// fix for multi-minute waits out of the box. +// See https://github.com/fluxcd/cli-utils/pull/20. +func WithStatusComputeWorkers(n int) WaitOption { + return func(wo *waitOptions) { + if n < 0 { + n = 0 + } + wo.statusComputeWorkers = n + } +} + type waitOptions struct { - ctx context.Context - watchUntilReadyCtx context.Context - waitCtx context.Context - waitWithJobsCtx context.Context - waitForDeleteCtx context.Context - statusReaders []engine.StatusReader + ctx context.Context + watchUntilReadyCtx context.Context + waitCtx context.Context + waitWithJobsCtx context.Context + waitForDeleteCtx context.Context + statusReaders []engine.StatusReader + statusComputeWorkers int } diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index 12c3e95a6..91eec30ef 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -43,14 +43,15 @@ import ( ) type statusWaiter struct { - client dynamic.Interface - restMapper meta.RESTMapper - ctx context.Context - watchUntilReadyCtx context.Context - waitCtx context.Context - waitWithJobsCtx context.Context - waitForDeleteCtx context.Context - readers []engine.StatusReader + client dynamic.Interface + restMapper meta.RESTMapper + ctx context.Context + watchUntilReadyCtx context.Context + waitCtx context.Context + waitWithJobsCtx context.Context + waitForDeleteCtx context.Context + readers []engine.StatusReader + statusComputeWorkers int logging.LogHolder } @@ -82,6 +83,7 @@ func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.D defer cancel() w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := getStatusWatcher(w.client, w.restMapper) + sw.StatusComputeWorkers = w.statusComputeWorkers jobSR := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) podSR := helmStatusReaders.NewCustomPodStatusReader(w.restMapper) // We don't want to wait on any other resources as watchUntilReady is only for Helm hooks. @@ -104,6 +106,7 @@ func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) er defer cancel() w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := getStatusWatcher(w.client, w.restMapper) + sw.StatusComputeWorkers = w.statusComputeWorkers sw.StatusReader = statusreaders.NewStatusReader(w.restMapper, w.readers...) return w.wait(ctx, resourceList, sw) } @@ -116,6 +119,7 @@ func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Dura defer cancel() w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := getStatusWatcher(w.client, w.restMapper) + sw.StatusComputeWorkers = w.statusComputeWorkers newCustomJobStatusReader := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) readers := append([]engine.StatusReader(nil), w.readers...) readers = append(readers, newCustomJobStatusReader) diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index 14f10124c..21580b57a 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -1293,6 +1293,28 @@ func TestWaitOptionFunctions(t *testing.T) { WithWaitForDeleteMethodContext(ctx)(opts) assert.Equal(t, ctx, opts.waitForDeleteCtx) }) + + t.Run("WithStatusComputeWorkers sets statusComputeWorkers", func(t *testing.T) { + t.Parallel() + opts := &waitOptions{} + WithStatusComputeWorkers(8)(opts) + assert.Equal(t, 8, opts.statusComputeWorkers) + }) + + t.Run("WithStatusComputeWorkers clamps negative values to zero", func(t *testing.T) { + t.Parallel() + opts := &waitOptions{} + WithStatusComputeWorkers(-1)(opts) + assert.Equal(t, 0, opts.statusComputeWorkers, + "negative worker counts must not propagate to the underlying watcher") + }) + + t.Run("waitOptions.statusComputeWorkers defaults to zero", func(t *testing.T) { + t.Parallel() + opts := &waitOptions{} + assert.Equal(t, 0, opts.statusComputeWorkers, + "SDK consumers must opt in to concurrent status computation") + }) } func TestMethodSpecificContextCancellation(t *testing.T) { From 0604d8fb1ee8ea172604cb739c93ea8d5ad068c6 Mon Sep 17 00:00:00 2001 From: Gates Wang <9372086+SetagGnaw@users.noreply.github.com> Date: Sun, 19 Jul 2026 20:36:57 -0400 Subject: [PATCH 449/571] fix: pass registry client to downloader.Manager in upgrade `helm upgrade --dependency-update` built its downloader.Manager without a RegistryClient, unlike the identical literals in install, dependency update, and dependency build. Resolving an OCI chart dependency whose version is a range then dereferenced a nil *registry.Client in registry.(*Client).Tags, panicking the process. Pass the registry client already constructed for the command, matching install. Add a regression test that runs `upgrade --dependency-update` against a chart with an unresolved OCI dependency and asserts the command returns an error instead of panicking. Signed-off-by: Gates Wang <9372086+SetagGnaw@users.noreply.github.com> --- pkg/cmd/upgrade.go | 1 + pkg/cmd/upgrade_test.go | 60 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index d0d0166ba..688842141 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -226,6 +226,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { ContentCache: settings.ContentCache, Debug: settings.Debug, SourceDateEpoch: sourceDateEpoch, + RegistryClient: registryClient, } if err := man.Update(); err != nil { return err diff --git a/pkg/cmd/upgrade_test.go b/pkg/cmd/upgrade_test.go index 30188d3d9..203ec19cc 100644 --- a/pkg/cmd/upgrade_test.go +++ b/pkg/cmd/upgrade_test.go @@ -18,6 +18,8 @@ package cmd import ( "fmt" + "net/http" + "net/http/httptest" "os" "path/filepath" "reflect" @@ -193,6 +195,64 @@ func TestUpgradeCmd(t *testing.T) { runTestCmd(t, tests) } +// TestUpgradeDependencyUpdateOCINoPanic is a regression test for a nil-pointer +// panic in `helm upgrade --dependency-update` when a chart declares an OCI +// dependency. The upgrade command built its downloader.Manager without a +// RegistryClient (unlike install, dependency update, and dependency build), so +// resolving an OCI dependency dereferenced a nil *registry.Client. The command +// must now return a graceful error instead of panicking. +func TestUpgradeDependencyUpdateOCINoPanic(t *testing.T) { + defer resetEnv()() + + // A stub registry that answers the API-version ping but rejects the tag + // lookup, so OCI dependency resolution fails fast and hermetically instead + // of reaching a real registry. + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Docker-Distribution-API-Version", "registry/2.0") + if r.URL.Path == "/v2/" { + w.WriteHeader(http.StatusOK) + return + } + w.WriteHeader(http.StatusNotFound) + })) + defer srv.Close() + + // A chart with an unresolved OCI dependency forces --dependency-update into + // the tag-lookup path that previously panicked: the version is a range (an + // explicit version would skip the lookup) and the dependency is not present + // under charts/. + tmp := t.TempDir() + parent := &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: chart.APIVersionV2, + Name: "oci-parent", + Version: "0.1.0", + Dependencies: []*chart.Dependency{{ + Name: "subchart", + Repository: fmt.Sprintf("oci://%s/charts", srv.Listener.Addr()), + Version: "^1.0.0", + }}, + }, + } + if err := chartutil.SaveDir(parent, tmp); err != nil { + t.Fatalf("Error creating chart: %v", err) + } + chartPath := filepath.Join(tmp, parent.Metadata.Name) + // SaveDir writes only resolved subcharts (Chart.Dependencies()), not the + // declared Metadata.Dependencies, so create the empty charts/ directory + // explicitly to make the "dependency missing from charts/" state concrete. + if err := os.MkdirAll(filepath.Join(chartPath, "charts"), 0o755); err != nil { + t.Fatalf("Error creating charts dir: %v", err) + } + + // The command must return an error (registry rejects the lookup), not panic. + _, _, err := executeActionCommandC(storageFixture(), + fmt.Sprintf("upgrade --dependency-update --plain-http oci-parent '%s'", chartPath)) + if err == nil { + t.Fatal("expected an error resolving the OCI dependency, got nil") + } +} + func TestUpgradeWithValue(t *testing.T) { releaseName := "funny-bunny-v2" relMock, ch, chartPath := prepareMockRelease(t, releaseName) From 615a29987660b11909f984887b47f7f608757d87 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:57 +0200 Subject: [PATCH 450/571] chore(internal): refactor: convert tests to testify assert/require part 2 #### Description refactor: convert tests to testify assert/require in internal/plugin/installer Signed-off-by: Matthieu MOREL --- internal/chart/v3/util/dependencies_test.go | 12 +- .../plugin/installer/http_installer_test.go | 290 ++++++------------ .../plugin/installer/local_installer_test.go | 80 ++--- 3 files changed, 116 insertions(+), 266 deletions(-) diff --git a/internal/chart/v3/util/dependencies_test.go b/internal/chart/v3/util/dependencies_test.go index f6ca06da3..45693ef46 100644 --- a/internal/chart/v3/util/dependencies_test.go +++ b/internal/chart/v3/util/dependencies_test.go @@ -369,8 +369,7 @@ func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) { require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - err := processDependencyEnabled(c, c.Values, "") - require.NoError(t, err, "expected no errors") + require.NoError(t, processDependencyEnabled(c, c.Values, ""), "expected no errors") require.Len(t, c.Dependencies(), 2, "expected no changes in dependencies") } @@ -381,8 +380,7 @@ func TestDependentChartWithSubChartsHelmignore(t *testing.T) { func TestDependentChartsWithSubChartsSymlink(t *testing.T) { joonix := filepath.Join("testdata", "joonix") - err := os.Symlink(filepath.Join("..", "..", "frobnitz"), filepath.Join(joonix, "charts", "frobnitz")) - require.NoError(t, err, "failed to create symlink") + require.NoError(t, os.Symlink(filepath.Join("..", "..", "frobnitz"), filepath.Join(joonix, "charts", "frobnitz")), "failed to create symlink") defer os.RemoveAll(filepath.Join(joonix, "charts", "frobnitz")) c := loadChart(t, joonix) @@ -394,12 +392,8 @@ func TestDependentChartsWithSubchartsAllSpecifiedInDependency(t *testing.T) { c := loadChart(t, "testdata/dependent-chart-with-all-in-requirements-yaml") require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - - err := processDependencyEnabled(c, c.Values, "") - require.NoError(t, err, "expected no errors") - + require.NoError(t, processDependencyEnabled(c, c.Values, ""), "expected no errors") require.Len(t, c.Dependencies(), 2, "expected no changes in dependencies") - require.Len(t, c.Dependencies(), len(c.Metadata.Dependencies), "expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies())) } diff --git a/internal/plugin/installer/http_installer_test.go b/internal/plugin/installer/http_installer_test.go index 3235e9796..d0d5e099d 100644 --- a/internal/plugin/installer/http_installer_test.go +++ b/internal/plugin/installer/http_installer_test.go @@ -27,10 +27,14 @@ import ( "net/http/httptest" "os" "path/filepath" + "strconv" "strings" "syscall" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/internal/test/ensure" "helm.sh/helm/v4/pkg/getter" "helm.sh/helm/v4/pkg/helmpath" @@ -52,18 +56,10 @@ func (t *TestHTTPGetter) Get(_ string, _ ...getter.Option) (*bytes.Buffer, error var fakePluginB64 = "H4sIAAAAAAAAA+3SQUvDMBgG4Jz7K0LwapdvSxrwJig6mCKC5xHabBaXdDSt4L+3cQ56mV42ZPg+lw+SF5LwZmXf3OV206/rMGEnIgdG6zTJaDmee4y01FOlZpqGHJGZSsb1qS401sfOtpyz0FTup9xv+2dqNep/N/IP6zdHPSMVXCh1sH8yhtGMDBUFFTL1r4iIcXnUWxzwz/sP1rsrLkbfQGTvro11E4ZlmcucRNZHu04py1OO73OVi2Vbb7td9vp7nXevtvsKRpGVjfc2VMP2xf3t4mH5tHi5mz8ub+bPk9JXIvvr5wMAAAAAAAAAAAAAAAAAAAAAnLVPqwHcXQAoAAA=" func TestStripName(t *testing.T) { - if stripPluginName("fake-plugin-0.0.1.tar.gz") != "fake-plugin" { - t.Error("name does not match expected value") - } - if stripPluginName("fake-plugin-0.0.1.tgz") != "fake-plugin" { - t.Error("name does not match expected value") - } - if stripPluginName("fake-plugin.tgz") != "fake-plugin" { - t.Error("name does not match expected value") - } - if stripPluginName("fake-plugin.tar.gz") != "fake-plugin" { - t.Error("name does not match expected value") - } + assert.Equal(t, "fake-plugin", stripPluginName("fake-plugin-0.0.1.tar.gz"), "name does not match expected value") + assert.Equal(t, "fake-plugin", stripPluginName("fake-plugin-0.0.1.tgz"), "name does not match expected value") + assert.Equal(t, "fake-plugin", stripPluginName("fake-plugin.tgz"), "name does not match expected value") + assert.Equal(t, "fake-plugin", stripPluginName("fake-plugin.tar.gz"), "name does not match expected value") } func mockArchiveServer() *httptest.Server { @@ -85,45 +81,29 @@ func TestHTTPInstaller(t *testing.T) { defer srv.Close() source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0o755); err != nil { - t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) - } + require.NoErrorf(t, os.MkdirAll(helmpath.DataPath("plugins"), 0o755), "Could not create %s", helmpath.DataPath("plugins")) i, err := NewForSource(source, "0.0.1") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } + require.NoError(t, err) // ensure a HTTPInstaller was returned httpInstaller, ok := i.(*HTTPInstaller) - if !ok { - t.Fatal("expected a HTTPInstaller") - } + require.True(t, ok, "expected a HTTPInstaller") // inject fake http client responding with minimal plugin tarball mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64) - if err != nil { - t.Fatalf("Could not decode fake tgz plugin: %s", err) - } + require.NoError(t, err, "Could not decode fake tgz plugin") httpInstaller.getter = &TestHTTPGetter{ MockResponse: bytes.NewBuffer(mockTgz), } // install the plugin - if err := Install(i); err != nil { - t.Fatal(err) - } - if i.Path() != helmpath.DataPath("plugins", "fake-plugin") { - t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) - } + require.NoError(t, Install(i)) + require.Equal(t, helmpath.DataPath("plugins", "fake-plugin"), i.Path(), "expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) // Install again to test plugin exists error - if err := Install(i); err == nil { - t.Fatal("expected error for plugin exists, got none") - } else if err.Error() != "plugin already exists" { - t.Fatalf("expected error for plugin exists, got (%v)", err) - } + require.EqualErrorf(t, Install(i), "plugin already exists", "expected error for plugin exists") } func TestHTTPInstallerNonExistentVersion(t *testing.T) { @@ -132,20 +112,14 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) { defer srv.Close() source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0o755); err != nil { - t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) - } + require.NoErrorf(t, os.MkdirAll(helmpath.DataPath("plugins"), 0o755), "Could not create %s", helmpath.DataPath("plugins")) i, err := NewForSource(source, "0.0.2") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } + require.NoError(t, err) // ensure a HTTPInstaller was returned httpInstaller, ok := i.(*HTTPInstaller) - if !ok { - t.Fatal("expected a HTTPInstaller") - } + require.True(t, ok, "expected a HTTPInstaller") // inject fake http client responding with error httpInstaller.getter = &TestHTTPGetter{ @@ -153,9 +127,7 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) { } // attempt to install the plugin - if err := Install(i); err == nil { - t.Fatal("expected error from http client") - } + require.Error(t, Install(i), "expected error from http client") } func TestHTTPInstallerUpdate(t *testing.T) { @@ -164,43 +136,29 @@ func TestHTTPInstallerUpdate(t *testing.T) { source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" ensure.HelmHome(t) - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0o755); err != nil { - t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) - } + require.NoErrorf(t, os.MkdirAll(helmpath.DataPath("plugins"), 0o755), "Could not create %s", helmpath.DataPath("plugins")) i, err := NewForSource(source, "0.0.1") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } + require.NoError(t, err) // ensure a HTTPInstaller was returned httpInstaller, ok := i.(*HTTPInstaller) - if !ok { - t.Fatal("expected a HTTPInstaller") - } + require.True(t, ok, "expected a HTTPInstaller") // inject fake http client responding with minimal plugin tarball mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64) - if err != nil { - t.Fatalf("Could not decode fake tgz plugin: %s", err) - } + require.NoError(t, err, "Could not decode fake tgz plugin") httpInstaller.getter = &TestHTTPGetter{ MockResponse: bytes.NewBuffer(mockTgz), } // install the plugin before updating - if err := Install(i); err != nil { - t.Fatal(err) - } - if i.Path() != helmpath.DataPath("plugins", "fake-plugin") { - t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) - } + require.NoError(t, Install(i)) + require.Equal(t, helmpath.DataPath("plugins", "fake-plugin"), i.Path(), "expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) // Update plugin, should fail because it is not implemented - if err := Update(i); err == nil { - t.Fatal("update method not implemented for http installer") - } + require.Error(t, Update(i), "update method not implemented for http installer") } func TestExtract(t *testing.T) { @@ -229,12 +187,9 @@ func TestExtract(t *testing.T) { Mode: file.Mode, Size: int64(len(file.Body)), } - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - if _, err := tw.Write([]byte(file.Body)); err != nil { - t.Fatal(err) - } + require.NoError(t, tw.WriteHeader(hdr)) + _, err := tw.Write([]byte(file.Body)) + require.NoError(t, err) } // Add pax global headers. This should be ignored. @@ -242,33 +197,24 @@ func TestExtract(t *testing.T) { // Details are in the internal Go function for the tar packaged named // allowedFormats. For a TypeXHeader it will return a message stating // "cannot manually encode TypeXHeader, TypeGNULongName, or TypeGNULongLink headers" - if err := tw.WriteHeader(&tar.Header{ + require.NoError(t, tw.WriteHeader(&tar.Header{ Name: "pax_global_header", Typeflag: tar.TypeXGlobalHeader, - }); err != nil { - t.Fatal(err) - } + })) - if err := tw.Close(); err != nil { - t.Fatal(err) - } + require.NoError(t, tw.Close()) var buf bytes.Buffer gz := gzip.NewWriter(&buf) - if _, err := gz.Write(tarbuf.Bytes()); err != nil { - t.Fatal(err) - } + _, err := gz.Write(tarbuf.Bytes()) + require.NoError(t, err) gz.Close() // END tarball creation extractor, err := NewExtractor(source) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if err = extractor.Extract(&buf, tempDir); err != nil { - t.Fatalf("Did not expect error but got error: %v", err) - } + require.NoErrorf(t, extractor.Extract(&buf, tempDir), "Did not expect error") // Calculate expected permissions after umask is applied expectedPluginYAMLPerm := os.FileMode(0o600 &^ currentUmask) @@ -276,23 +222,19 @@ func TestExtract(t *testing.T) { pluginYAMLFullPath := filepath.Join(tempDir, "plugin.yaml") if info, err := os.Stat(pluginYAMLFullPath); err != nil { - if errors.Is(err, fs.ErrNotExist) { - t.Fatalf("Expected %s to exist but doesn't", pluginYAMLFullPath) - } + require.NotErrorIs(t, err, fs.ErrNotExist, "Expected %s to exist but doesn't", pluginYAMLFullPath) t.Fatal(err) - } else if info.Mode().Perm() != expectedPluginYAMLPerm { - t.Fatalf("Expected %s to have %o mode but has %o (umask: %o)", + } else { + require.Equalf(t, expectedPluginYAMLPerm, info.Mode().Perm(), "Expected %s to have %o mode but has %o (umask: %o)", pluginYAMLFullPath, expectedPluginYAMLPerm, info.Mode().Perm(), currentUmask) } readmeFullPath := filepath.Join(tempDir, "README.md") if info, err := os.Stat(readmeFullPath); err != nil { - if errors.Is(err, fs.ErrNotExist) { - t.Fatalf("Expected %s to exist but doesn't", readmeFullPath) - } + require.NotErrorIs(t, err, fs.ErrNotExist, "Expected %s to exist but doesn't", readmeFullPath) t.Fatal(err) - } else if info.Mode().Perm() != expectedReadmePerm { - t.Fatalf("Expected %s to have %o mode but has %o (umask: %o)", + } else { + require.Equalf(t, expectedReadmePerm, info.Mode().Perm(), "Expected %s to have %o mode but has %o (umask: %o)", readmeFullPath, expectedReadmePerm, info.Mode().Perm(), currentUmask) } } @@ -313,16 +255,15 @@ func TestCleanJoin(t *testing.T) { {"foo\\bar.txt", "/tmp/foo/bar.txt", false}, {"c:\\foo\\bar.txt", "", true}, } { - out, err := cleanJoin("/tmp", fixture.path) - if err != nil { - if !fixture.expectError { - t.Errorf("Test %d: Path was not cleaned: %s", i, err) + t.Run(strconv.Itoa(i), func(t *testing.T) { + out, err := cleanJoin("/tmp", fixture.path) + if fixture.expectError { + require.Error(t, err, "Test %d: Path was not cleaned", i) + } else { + require.NoError(t, err) + assert.Equal(t, fixture.expect, out, "Test %d: Expected %q but got %q", i, fixture.expect, out) } - continue - } - if fixture.expect != out { - t.Errorf("Test %d: Expected %q but got %q", i, fixture.expect, out) - } + }) } } @@ -336,14 +277,11 @@ func TestMediaTypeToExtension(t *testing.T) { "application/json": false, } { ext, ok := mediaTypeToExtension(mt) - if ok != shouldPass { - t.Errorf("Media type %q failed test", mt) - } - if shouldPass && ext == "" { - t.Error("Expected an extension but got empty string") - } - if !shouldPass && ext != "" { - t.Error("Expected extension to be empty for unrecognized type") + assert.Equal(t, shouldPass, ok, "Media type %q failed test", mt) + if shouldPass { + assert.NotEmpty(t, ext, "Expected an extension but got empty string for media type %q", mt) + } else { + assert.Empty(t, ext, "Expected extension to be empty for unrecognized media type %q", mt) } } } @@ -377,55 +315,41 @@ func TestExtractWithNestedDirectories(t *testing.T) { Mode: file.Mode, Size: int64(len(file.Body)), } - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } + require.NoError(t, tw.WriteHeader(hdr)) if file.TypeFlag == tar.TypeReg { - if _, err := tw.Write([]byte(file.Body)); err != nil { - t.Fatal(err) - } + _, err := tw.Write([]byte(file.Body)) + require.NoError(t, err) } } - if err := tw.Close(); err != nil { - t.Fatal(err) - } + require.NoError(t, tw.Close()) var buf bytes.Buffer gz := gzip.NewWriter(&buf) - if _, err := gz.Write(tarbuf.Bytes()); err != nil { - t.Fatal(err) - } + _, err := gz.Write(tarbuf.Bytes()) + require.NoError(t, err) gz.Close() extractor, err := NewExtractor(source) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // First extraction - if err = extractor.Extract(&buf, tempDir); err != nil { - t.Fatalf("First extraction failed: %v", err) - } + require.NoError(t, extractor.Extract(&buf, tempDir), "First extraction failed") // Verify nested structure was created nestedFile := filepath.Join(tempDir, "docs", "examples", "example1.yaml") - if _, err := os.Stat(nestedFile); err != nil { - t.Fatalf("Expected nested file %s to exist but got error: %v", nestedFile, err) - } + _, err = os.Stat(nestedFile) + require.NoErrorf(t, err, "Expected nested file %s to exist", nestedFile) // Reset buffer for second extraction buf.Reset() gz = gzip.NewWriter(&buf) - if _, err := gz.Write(tarbuf.Bytes()); err != nil { - t.Fatal(err) - } + _, err = gz.Write(tarbuf.Bytes()) + require.NoError(t, err) gz.Close() // Second extraction to same directory (should not fail) - if err = extractor.Extract(&buf, tempDir); err != nil { - t.Fatalf("Second extraction to existing directory failed: %v", err) - } + require.NoErrorf(t, extractor.Extract(&buf, tempDir), "Second extraction to existing directory failed") } func TestExtractWithExistingDirectory(t *testing.T) { @@ -434,15 +358,11 @@ func TestExtractWithExistingDirectory(t *testing.T) { // Pre-create the cache directory structure cacheDir := filepath.Join(tempDir, "cache") - if err := os.MkdirAll(filepath.Join(cacheDir, "existing", "dir"), 0o755); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(filepath.Join(cacheDir, "existing", "dir"), 0o755)) // Create a file in the existing directory existingFile := filepath.Join(cacheDir, "existing", "file.txt") - if err := os.WriteFile(existingFile, []byte("existing content"), 0o644); err != nil { - t.Fatal(err) - } + require.NoError(t, os.WriteFile(existingFile, []byte("existing content"), 0o644)) // Write a tarball var tarbuf bytes.Buffer @@ -466,47 +386,35 @@ func TestExtractWithExistingDirectory(t *testing.T) { Mode: file.Mode, Size: int64(len(file.Body)), } - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } + require.NoError(t, tw.WriteHeader(hdr)) if file.TypeFlag == tar.TypeReg { - if _, err := tw.Write([]byte(file.Body)); err != nil { - t.Fatal(err) - } + _, err := tw.Write([]byte(file.Body)) + require.NoError(t, err) } } - if err := tw.Close(); err != nil { - t.Fatal(err) - } + require.NoError(t, tw.Close()) var buf bytes.Buffer gz := gzip.NewWriter(&buf) - if _, err := gz.Write(tarbuf.Bytes()); err != nil { - t.Fatal(err) - } + _, err := gz.Write(tarbuf.Bytes()) + require.NoError(t, err) gz.Close() extractor, err := NewExtractor(source) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Extract to directory with existing content - if err = extractor.Extract(&buf, cacheDir); err != nil { - t.Fatalf("Extraction to directory with existing content failed: %v", err) - } + require.NoErrorf(t, extractor.Extract(&buf, cacheDir), "Extraction to directory with existing content failed") // Verify new file was created newFile := filepath.Join(cacheDir, "existing", "dir", "newfile.txt") - if _, err := os.Stat(newFile); err != nil { - t.Fatalf("Expected new file %s to exist but got error: %v", newFile, err) - } + _, err = os.Stat(newFile) + require.NoErrorf(t, err, "Expected new file %s to exist but got error", newFile) // Verify existing file is still there - if _, err := os.Stat(existingFile); err != nil { - t.Fatalf("Expected existing file %s to still exist but got error: %v", existingFile, err) - } + _, err = os.Stat(existingFile) + require.NoErrorf(t, err, "Expected existing file %s to still exist", existingFile) } func TestExtractPluginInSubdirectory(t *testing.T) { @@ -536,25 +444,19 @@ func TestExtractPluginInSubdirectory(t *testing.T) { Mode: file.Mode, Size: int64(len(file.Body)), } - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } + require.NoError(t, tw.WriteHeader(hdr)) if file.TypeFlag == tar.TypeReg { - if _, err := tw.Write([]byte(file.Body)); err != nil { - t.Fatal(err) - } + _, err := tw.Write([]byte(file.Body)) + require.NoError(t, err) } } - if err := tw.Close(); err != nil { - t.Fatal(err) - } + require.NoError(t, tw.Close()) var buf bytes.Buffer gz := gzip.NewWriter(&buf) - if _, err := gz.Write(tarbuf.Bytes()); err != nil { - t.Fatal(err) - } + _, err := gz.Write(tarbuf.Bytes()) + require.NoError(t, err) gz.Close() // Test the installer @@ -573,24 +475,16 @@ func TestExtractPluginInSubdirectory(t *testing.T) { // Ensure the destination directory doesn't exist // (In a real scenario, this is handled by installer.Install() wrapper) destPath := installer.Path() - if err := os.RemoveAll(destPath); err != nil { - t.Fatalf("Failed to clean destination path: %v", err) - } + require.NoErrorf(t, os.RemoveAll(destPath), "Failed to clean destination path") // Install should handle the subdirectory correctly - if err := installer.Install(); err != nil { - t.Fatalf("Failed to install plugin with subdirectory: %v", err) - } + require.NoErrorf(t, installer.Install(), "Failed to install plugin with subdirectory") // The plugin should be installed from the subdirectory // Check that detectPluginRoot found the correct location pluginRoot, err := detectPluginRoot(tempDir) - if err != nil { - t.Fatalf("Failed to detect plugin root: %v", err) - } + require.NoError(t, err, "Failed to detect plugin root") expectedRoot := filepath.Join(tempDir, "my-plugin") - if pluginRoot != expectedRoot { - t.Errorf("Expected plugin root to be %s but got %s", expectedRoot, pluginRoot) - } + assert.Equal(t, expectedRoot, pluginRoot, "Expected plugin root to be %s but got %s", expectedRoot, pluginRoot) } diff --git a/internal/plugin/installer/local_installer_test.go b/internal/plugin/installer/local_installer_test.go index f46fcb44b..94632756c 100644 --- a/internal/plugin/installer/local_installer_test.go +++ b/internal/plugin/installer/local_installer_test.go @@ -19,11 +19,12 @@ import ( "archive/tar" "bytes" "compress/gzip" - "errors" "os" "path/filepath" "testing" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/internal/test/ensure" "helm.sh/helm/v4/pkg/helmpath" ) @@ -34,40 +35,23 @@ func TestLocalInstaller(t *testing.T) { ensure.HelmHome(t) // Make a temp dir tdir := t.TempDir() - if err := os.WriteFile(filepath.Join(tdir, "plugin.yaml"), []byte{}, 0o644); err != nil { - t.Fatal(err) - } + require.NoError(t, os.WriteFile(filepath.Join(tdir, "plugin.yaml"), []byte{}, 0o644)) source := "../testdata/plugdir/good/echo-v1" i, err := NewForSource(source, "") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } + require.NoError(t, err) - if err := Install(i); err != nil { - t.Fatal(err) - } + require.NoError(t, Install(i)) - if i.Path() != helmpath.DataPath("plugins", "echo-v1") { - t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) - } + require.Equal(t, helmpath.DataPath("plugins", "echo-v1"), i.Path(), "expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) os.RemoveAll(filepath.Dir(helmpath.DataPath())) // helmpath.DataPath is like /tmp/helm013130971/helm } func TestLocalInstallerNotAFolder(t *testing.T) { source := "../testdata/plugdir/good/echo-v1/plugin.yaml" i, err := NewForSource(source, "") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - err = Install(i) - if err == nil { - t.Fatal("expected error") - } - if !errors.Is(err, ErrPluginNotADirectory) { - t.Fatalf("expected error to equal: %q", err) - } + require.NoError(t, err) + require.ErrorIs(t, Install(i), ErrPluginNotADirectory) } func TestLocalInstallerTarball(t *testing.T) { @@ -97,53 +81,31 @@ func TestLocalInstallerTarball(t *testing.T) { Mode: file.Mode, Size: int64(len(file.Body)), } - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - if _, err := tw.Write([]byte(file.Body)); err != nil { - t.Fatal(err) - } + require.NoError(t, tw.WriteHeader(hdr)) + _, err := tw.Write([]byte(file.Body)) + require.NoError(t, err) } - if err := tw.Close(); err != nil { - t.Fatal(err) - } - if err := gw.Close(); err != nil { - t.Fatal(err) - } + require.NoError(t, tw.Close()) + require.NoError(t, gw.Close()) // Write tarball to file - if err := os.WriteFile(tarballPath, buf.Bytes(), 0o644); err != nil { - t.Fatal(err) - } + require.NoError(t, os.WriteFile(tarballPath, buf.Bytes(), 0o644)) // Test installation i, err := NewForSource(tarballPath, "") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } + require.NoError(t, err) // Verify it's detected as LocalInstaller localInstaller, ok := i.(*LocalInstaller) - if !ok { - t.Fatal("expected LocalInstaller") - } - - if !localInstaller.isArchive { - t.Fatal("expected isArchive to be true") - } - - if err := Install(i); err != nil { - t.Fatal(err) - } + require.True(t, ok, "expected LocalInstaller") + require.True(t, localInstaller.isArchive, "expected isArchive to be true") + require.NoError(t, Install(i)) expectedPath := helmpath.DataPath("plugins", "test-plugin") - if i.Path() != expectedPath { - t.Fatalf("expected path %q, got %q", expectedPath, i.Path()) - } + require.Equal(t, expectedPath, i.Path(), "expected path %q, got %q", expectedPath, i.Path()) // Verify plugin was installed - if _, err := os.Stat(i.Path()); err != nil { - t.Fatalf("plugin not found at %s: %v", i.Path(), err) - } + _, err = os.Stat(i.Path()) + require.NoErrorf(t, err, "plugin not found at %s", i.Path()) } From ba31ca0c0b455541f05986e8dcb8a0554fa70dd2 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:56 +0200 Subject: [PATCH 451/571] chore(internal): refactor: convert tests to testify assert/require part 1 refactor: convert tests to testify assert/require in internal/plugin/installer Signed-off-by: Matthieu MOREL --- .../plugin/installer/oci_installer_test.go | 316 +++++------------- .../plugin/installer/vcs_installer_test.go | 89 ++--- 2 files changed, 113 insertions(+), 292 deletions(-) diff --git a/internal/plugin/installer/oci_installer_test.go b/internal/plugin/installer/oci_installer_test.go index 48eccf6e1..ce381d12c 100644 --- a/internal/plugin/installer/oci_installer_test.go +++ b/internal/plugin/installer/oci_installer_test.go @@ -64,12 +64,9 @@ command: "$HELM_PLUGIN_DIR/bin/%s" Size: int64(len(pluginYAML)), Typeflag: tar.TypeReg, } - if err := tarWriter.WriteHeader(header); err != nil { - t.Fatal(err) - } - if _, err := tarWriter.Write([]byte(pluginYAML)); err != nil { - t.Fatal(err) - } + require.NoError(t, tarWriter.WriteHeader(header)) + _, err := tarWriter.Write([]byte(pluginYAML)) + require.NoError(t, err) // Add bin directory dirHeader := &tar.Header{ @@ -77,9 +74,7 @@ command: "$HELM_PLUGIN_DIR/bin/%s" Mode: 0o755, Typeflag: tar.TypeDir, } - if err := tarWriter.WriteHeader(dirHeader); err != nil { - t.Fatal(err) - } + require.NoError(t, tarWriter.WriteHeader(dirHeader)) // Add executable execContent := fmt.Sprintf("#!/bin/sh\necho '%s test plugin'", pluginName) @@ -89,12 +84,9 @@ command: "$HELM_PLUGIN_DIR/bin/%s" Size: int64(len(execContent)), Typeflag: tar.TypeReg, } - if err := tarWriter.WriteHeader(execHeader); err != nil { - t.Fatal(err) - } - if _, err := tarWriter.Write([]byte(execContent)); err != nil { - t.Fatal(err) - } + require.NoError(t, tarWriter.WriteHeader(execHeader)) + _, err = tarWriter.Write([]byte(execContent)) + require.NoError(t, err) tarWriter.Close() gzWriter.Close() @@ -135,9 +127,7 @@ func mockOCIRegistryWithArtifactType(t *testing.T, pluginName string) (*httptest } manifestData, err := json.Marshal(manifest) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) manifestDigest := fmt.Sprintf("sha256:%x", sha256Sum(manifestData)) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -175,9 +165,7 @@ func mockOCIRegistryWithArtifactType(t *testing.T, pluginName string) (*httptest // Parse server URL to get host:port format for OCI reference serverURL, err := url.Parse(server.URL) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) registryHost := serverURL.Host return server, registryHost @@ -240,42 +228,20 @@ func TestNewOCIInstaller(t *testing.T) { installer, err := NewOCIInstaller(tt.source) if tt.expectError { - if err == nil { - t.Error("expected error but got none") - } - return - } - - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - // Check all fields thoroughly - if installer.PluginName != tt.expectName { - t.Errorf("expected plugin name %s, got %s", tt.expectName, installer.PluginName) - } - - if installer.Source != tt.source { - t.Errorf("expected source %s, got %s", tt.source, installer.Source) - } - - if installer.CacheDir == "" { - t.Error("expected non-empty cache directory") - } - - if !strings.Contains(installer.CacheDir, "plugins") { - t.Errorf("expected cache directory to contain 'plugins', got %s", installer.CacheDir) - } - - if installer.settings == nil { - t.Error("expected settings to be initialized") - } - - // Check that Path() method works - expectedPath := helmpath.DataPath("plugins", tt.expectName) - if installer.Path() != expectedPath { - t.Errorf("expected path %s, got %s", expectedPath, installer.Path()) + require.Error(t, err) + } else { + require.NoError(t, err) + + // Check all fields thoroughly + assert.Equal(t, tt.expectName, installer.PluginName, "expected plugin name %s, got %s", tt.expectName, installer.PluginName) + assert.Equal(t, tt.source, installer.Source, "expected source %s, got %s", tt.source, installer.Source) + assert.NotEmpty(t, installer.CacheDir, "expected non-empty cache directory") + assert.Contains(t, installer.CacheDir, "plugins", "expected cache directory to contain 'plugins', got %s", installer.CacheDir) + assert.NotNil(t, installer.settings, "expected settings to be initialized") + + // Check that Path() method works + expectedPath := helmpath.DataPath("plugins", tt.expectName) + assert.Equal(t, expectedPath, installer.Path(), "expected path %s, got %s", expectedPath, installer.Path()) } }) } @@ -311,9 +277,7 @@ func TestOCIInstaller_Path(t *testing.T) { } path := installer.Path() - if path != tt.expectPath { - t.Errorf("expected path %s, got %s", tt.expectPath, path) - } + assert.Equal(t, tt.expectPath, path, "expected path %s, got %s", tt.expectPath, path) }) } } @@ -331,39 +295,30 @@ func TestOCIInstaller_Install(t *testing.T) { // Test with plain HTTP (since test server uses HTTP) installer, err := NewOCIInstaller(source, getter.WithPlainHTTP(true)) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } + require.NoError(t, err) // The OCI installer uses helmpath.DataPath, which is isolated by ensure.HelmHome(t) actualPath := installer.Path() t.Logf("Installer will use path: %s", actualPath) // Install the plugin - if err := Install(installer); err != nil { - t.Fatalf("Expected installation to succeed, got error: %v", err) - } + require.NoErrorf(t, Install(installer), "Expected installation to succeed") // Verify plugin was installed to the correct location - if !isPlugin(actualPath) { - t.Errorf("Expected plugin directory %s to contain plugin.yaml", actualPath) - } + assert.Truef(t, isPlugin(actualPath), "Expected plugin directory %s to contain plugin.yaml", actualPath) // Debug: list what was actually created - if entries, err := os.ReadDir(actualPath); err != nil { - t.Fatalf("Could not read plugin directory %s: %v", actualPath, err) - } else { - t.Logf("Plugin directory %s contains:", actualPath) - for _, entry := range entries { - t.Logf(" - %s", entry.Name()) - } + entries, err := os.ReadDir(actualPath) + require.NoError(t, err, "Could not read plugin directory %s", actualPath) + t.Logf("Plugin directory %s contains:", actualPath) + for _, entry := range entries { + t.Logf(" - %s", entry.Name()) } // Verify the plugin.yaml file exists and is valid pluginFile := filepath.Join(actualPath, "plugin.yaml") - if _, err := os.Stat(pluginFile); err != nil { - t.Errorf("Expected plugin.yaml to exist, got error: %v", err) - } + _, err = os.Stat(pluginFile) + assert.NoErrorf(t, err, "Expected plugin.yaml to exist") } func TestOCIInstaller_Install_WithGetterOptions(t *testing.T) { @@ -404,11 +359,9 @@ func TestOCIInstaller_Install_WithGetterOptions(t *testing.T) { source := fmt.Sprintf("oci://%s/%s:latest", registryHost, tc.pluginName) installer, err := NewOCIInstaller(source, tc.options...) - if err != nil { - if !tc.wantErr { - t.Fatalf("Expected no error creating installer, got %v", err) - } - return + + if !tc.wantErr { + require.NoError(t, err, "Expected no error creating installer") } // The installer now uses our isolated test directory @@ -419,7 +372,7 @@ func TestOCIInstaller_Install_WithGetterOptions(t *testing.T) { if tc.wantErr { require.Error(t, err, "Expected installation to fail, but it succeeded") } else { - require.NoError(t, err, "Expected installation to succeed, got error: %v", err) + require.NoError(t, err, "Expected installation to succeed") // Verify plugin was installed to the actual path assert.True(t, isPlugin(actualPath), "Expected plugin directory %s to contain plugin.yaml", actualPath) } @@ -437,27 +390,16 @@ func TestOCIInstaller_Install_AlreadyExists(t *testing.T) { source := fmt.Sprintf("oci://%s/%s:latest", registryHost, pluginName) installer, err := NewOCIInstaller(source, getter.WithPlainHTTP(true)) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } + require.NoError(t, err) // First install should succeed - if err := Install(installer); err != nil { - t.Fatalf("Expected first installation to succeed, got error: %v", err) - } + require.NoErrorf(t, Install(installer), "Expected first installation to succeed") // Verify plugin was installed - if !isPlugin(installer.Path()) { - t.Errorf("Expected plugin directory %s to contain plugin.yaml", installer.Path()) - } + assert.Truef(t, isPlugin(installer.Path()), "Expected plugin directory %s to contain plugin.yaml", installer.Path()) // Second install should fail with "plugin already exists" - err = Install(installer) - if err == nil { - t.Error("Expected error when installing plugin that already exists") - } else if !strings.Contains(err.Error(), "plugin already exists") { - t.Errorf("Expected 'plugin already exists' error, got: %v", err) - } + assert.ErrorContains(t, Install(installer), "plugin already exists") } func TestOCIInstaller_Update(t *testing.T) { @@ -470,38 +412,23 @@ func TestOCIInstaller_Update(t *testing.T) { source := fmt.Sprintf("oci://%s/%s:latest", registryHost, pluginName) installer, err := NewOCIInstaller(source, getter.WithPlainHTTP(true)) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } + require.NoError(t, err) // Test update when plugin does not exist - should fail - err = Update(installer) - if err == nil { - t.Error("Expected error when updating plugin that does not exist") - } else if !strings.Contains(err.Error(), "plugin does not exist") { - t.Errorf("Expected 'plugin does not exist' error, got: %v", err) - } + require.ErrorContains(t, Update(installer), "plugin does not exist") // Install plugin first - if err := Install(installer); err != nil { - t.Fatalf("Expected installation to succeed, got error: %v", err) - } + require.NoErrorf(t, Install(installer), "Expected installation to succeed") // Verify plugin was installed - if !isPlugin(installer.Path()) { - t.Errorf("Expected plugin directory %s to contain plugin.yaml", installer.Path()) - } + assert.Truef(t, isPlugin(installer.Path()), "Expected plugin directory %s to contain plugin.yaml", installer.Path()) // Test update when plugin exists - should succeed // For OCI, Update() removes old version and reinstalls - if err := Update(installer); err != nil { - t.Errorf("Expected update to succeed, got error: %v", err) - } + require.NoErrorf(t, Update(installer), "Expected update to succeed") // Verify plugin is still installed after update - if !isPlugin(installer.Path()) { - t.Errorf("Expected plugin directory %s to contain plugin.yaml after update", installer.Path()) - } + assert.Truef(t, isPlugin(installer.Path()), "Expected plugin directory %s to contain plugin.yaml after update", installer.Path()) } func TestOCIInstaller_Install_ComponentExtraction(t *testing.T) { @@ -513,35 +440,26 @@ func TestOCIInstaller_Install_ComponentExtraction(t *testing.T) { pluginData := createTestPluginTarGz(t, pluginName) // Test extraction - err := extractTarGz(bytes.NewReader(pluginData), tempDir) - if err != nil { - t.Fatalf("Failed to extract plugin: %v", err) - } + require.NoError(t, extractTarGz(bytes.NewReader(pluginData), tempDir), "Failed to extract plugin") // Verify plugin.yaml exists pluginYAMLPath := filepath.Join(tempDir, "plugin.yaml") - if _, err := os.Stat(pluginYAMLPath); os.IsNotExist(err) { - t.Error("plugin.yaml not found after extraction") - } + _, err := os.Stat(pluginYAMLPath) + assert.False(t, os.IsNotExist(err), "plugin.yaml not found after extraction") // Verify bin directory exists binPath := filepath.Join(tempDir, "bin") - if _, err := os.Stat(binPath); os.IsNotExist(err) { - t.Error("bin directory not found after extraction") - } + _, err = os.Stat(binPath) + assert.False(t, os.IsNotExist(err), "bin directory not found after extraction") // Verify executable exists and has correct permissions execPath := filepath.Join(tempDir, "bin", pluginName) - if info, err := os.Stat(execPath); err != nil { - t.Errorf("executable not found: %v", err) - } else if info.Mode()&0o111 == 0 { - t.Error("file is not executable") - } + info, err := os.Stat(execPath) + require.NoError(t, err, "executable not found") + assert.NotEqual(t, 0, info.Mode()&0o111, "file is not executable") // Verify this would be recognized as a plugin - if !isPlugin(tempDir) { - t.Error("extracted directory is not a valid plugin") - } + assert.True(t, isPlugin(tempDir), "extracted directory is not a valid plugin") } func TestExtractTarGz(t *testing.T) { @@ -561,13 +479,10 @@ func TestExtractTarGz(t *testing.T) { Typeflag: tar.TypeReg, } - if err := tarWriter.WriteHeader(header); err != nil { - t.Fatal(err) - } + require.NoError(t, tarWriter.WriteHeader(header)) - if _, err := tarWriter.Write([]byte(testContent)); err != nil { - t.Fatal(err) - } + _, err := tarWriter.Write([]byte(testContent)) + require.NoError(t, err) // Add a test directory dirHeader := &tar.Header{ @@ -576,35 +491,25 @@ func TestExtractTarGz(t *testing.T) { Typeflag: tar.TypeDir, } - if err := tarWriter.WriteHeader(dirHeader); err != nil { - t.Fatal(err) - } + require.NoError(t, tarWriter.WriteHeader(dirHeader)) tarWriter.Close() gzWriter.Close() // Test extraction - err := extractTarGz(bytes.NewReader(buf.Bytes()), tempDir) - if err != nil { - t.Errorf("extractTarGz failed: %v", err) - } + require.NoError(t, extractTarGz(bytes.NewReader(buf.Bytes()), tempDir), "extractTarGz failed") // Verify extracted file extractedFile := filepath.Join(tempDir, "test-file.txt") content, err := os.ReadFile(extractedFile) - if err != nil { - t.Errorf("failed to read extracted file: %v", err) - } + require.NoError(t, err, "failed to read extracted file") - if string(content) != testContent { - t.Errorf("expected content %s, got %s", testContent, string(content)) - } + assert.Equal(t, testContent, string(content), "expected content %s, got %s", testContent, string(content)) // Verify extracted directory extractedDir := filepath.Join(tempDir, "test-dir") - if _, err := os.Stat(extractedDir); os.IsNotExist(err) { - t.Errorf("extracted directory does not exist: %s", extractedDir) - } + _, err = os.Stat(extractedDir) + assert.Falsef(t, os.IsNotExist(err), "extracted directory does not exist: %s", extractedDir) } func TestExtractTarGz_InvalidGzip(t *testing.T) { @@ -612,10 +517,7 @@ func TestExtractTarGz_InvalidGzip(t *testing.T) { // Test with invalid gzip data invalidGzipData := []byte("not gzip data") - err := extractTarGz(bytes.NewReader(invalidGzipData), tempDir) - if err == nil { - t.Error("expected error for invalid gzip data") - } + assert.Error(t, extractTarGz(bytes.NewReader(invalidGzipData), tempDir), "expected error for invalid gzip data") } func TestExtractTar_UnknownFileType(t *testing.T) { @@ -634,13 +536,10 @@ func TestExtractTar_UnknownFileType(t *testing.T) { Typeflag: tar.TypeReg, } - if err := tarWriter.WriteHeader(header); err != nil { - t.Fatal(err) - } + require.NoError(t, tarWriter.WriteHeader(header)) - if _, err := tarWriter.Write([]byte(testContent)); err != nil { - t.Fatal(err) - } + _, err := tarWriter.Write([]byte(testContent)) + require.NoError(t, err) // Test unknown file type unknownHeader := &tar.Header{ @@ -649,21 +548,12 @@ func TestExtractTar_UnknownFileType(t *testing.T) { Typeflag: tar.TypeSymlink, // Use a type that's not handled } - if err := tarWriter.WriteHeader(unknownHeader); err != nil { - t.Fatal(err) - } + require.NoError(t, tarWriter.WriteHeader(unknownHeader)) tarWriter.Close() // Test extraction - should fail due to unknown type - err := extractTar(bytes.NewReader(buf.Bytes()), tempDir) - if err == nil { - t.Error("expected error for unknown tar file type") - } - - if !strings.Contains(err.Error(), "unknown type") { - t.Errorf("expected 'unknown type' error, got: %v", err) - } + assert.ErrorContains(t, extractTar(bytes.NewReader(buf.Bytes()), tempDir), "unknown type") } func TestExtractTar_SuccessfulExtraction(t *testing.T) { @@ -686,32 +576,22 @@ func TestExtractTar_SuccessfulExtraction(t *testing.T) { Typeflag: tar.TypeReg, } - if err := tarWriter.WriteHeader(header); err != nil { - t.Fatal(err) - } + require.NoError(t, tarWriter.WriteHeader(header)) - if _, err := tarWriter.Write([]byte(testContent)); err != nil { - t.Fatal(err) - } + _, err := tarWriter.Write([]byte(testContent)) + require.NoError(t, err) tarWriter.Close() // Test extraction - err := extractTar(bytes.NewReader(buf.Bytes()), tempDir) - if err != nil { - t.Errorf("extractTar failed: %v", err) - } + require.NoError(t, extractTar(bytes.NewReader(buf.Bytes()), tempDir), "extractTar failed") // Verify the regular file was extracted extractedFile := filepath.Join(tempDir, "test-file.txt") content, err := os.ReadFile(extractedFile) - if err != nil { - t.Errorf("failed to read extracted file: %v", err) - } + require.NoError(t, err, "failed to read extracted file") - if string(content) != testContent { - t.Errorf("expected content %s, got %s", testContent, string(content)) - } + assert.Equal(t, testContent, string(content), "expected content %s, got %s", testContent, string(content)) } func TestOCIInstaller_Install_PlainHTTPOption(t *testing.T) { @@ -720,41 +600,25 @@ func TestOCIInstaller_Install_PlainHTTPOption(t *testing.T) { // Test with PlainHTTP=false (default) installer1, err := NewOCIInstaller(source) - if err != nil { - t.Fatalf("failed to create installer: %v", err) - } - if installer1.getter == nil { - t.Error("getter should be initialized") - } + require.NoError(t, err, "failed to create installer") + assert.NotNil(t, installer1.getter, "getter should be initialized") // Test with PlainHTTP=true installer2, err := NewOCIInstaller(source, getter.WithPlainHTTP(true)) - if err != nil { - t.Fatalf("failed to create installer with PlainHTTP=true: %v", err) - } - if installer2.getter == nil { - t.Error("getter should be initialized with PlainHTTP=true") - } + require.NoError(t, err, "failed to create installer with PlainHTTP=true") + assert.NotNil(t, installer2.getter, "getter should be initialized with PlainHTTP=true") // Both installers should have the same basic properties - if installer1.PluginName != installer2.PluginName { - t.Error("plugin names should match") - } - if installer1.Source != installer2.Source { - t.Error("sources should match") - } + assert.Equal(t, installer2.PluginName, installer1.PluginName, "plugin names should match") + assert.Equal(t, installer2.Source, installer1.Source, "sources should match") // Test with multiple options installer3, err := NewOCIInstaller(source, getter.WithPlainHTTP(true), getter.WithBasicAuth("user", "pass"), ) - if err != nil { - t.Fatalf("failed to create installer with multiple options: %v", err) - } - if installer3.getter == nil { - t.Error("getter should be initialized with multiple options") - } + require.NoError(t, err, "failed to create installer with multiple options") + assert.NotNil(t, installer3.getter, "getter should be initialized with multiple options") } func TestOCIInstaller_Install_ValidationErrors(t *testing.T) { @@ -789,12 +653,8 @@ func TestOCIInstaller_Install_ValidationErrors(t *testing.T) { // Test the gzip validation logic that's used in the Install method if len(tt.layerData) < 2 || tt.layerData[0] != 0x1f || tt.layerData[1] != 0x8b { // This matches the validation in the Install method - if !tt.expectError { - t.Error("expected valid gzip data") - } - if !strings.Contains(tt.errorMsg, "is not a gzip compressed archive") { - t.Error("expected error message to contain 'is not a gzip compressed archive'") - } + assert.True(t, tt.expectError, "expected valid gzip data") + assert.Contains(t, tt.errorMsg, "is not a gzip compressed archive") } }) } diff --git a/internal/plugin/installer/vcs_installer_test.go b/internal/plugin/installer/vcs_installer_test.go index 7e44cc957..3f5b098e4 100644 --- a/internal/plugin/installer/vcs_installer_test.go +++ b/internal/plugin/installer/vcs_installer_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/Masterminds/vcs" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/internal/test/ensure" "helm.sh/helm/v4/pkg/helmpath" @@ -52,9 +53,7 @@ func (r *testRepo) UpdateVersion(version string) error { func TestVCSInstaller(t *testing.T) { ensure.HelmHome(t) - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0o755); err != nil { - t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) - } + require.NoErrorf(t, os.MkdirAll(helmpath.DataPath("plugins"), 0o755), "Could not create %s", helmpath.DataPath("plugins")) source := "https://github.com/adamreese/helm-env" testRepoPath, _ := filepath.Abs("../testdata/plugdir/good/echo-v1") @@ -64,43 +63,27 @@ func TestVCSInstaller(t *testing.T) { } i, err := NewForSource(source, "~0.1.0") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } + require.NoError(t, err) // ensure a VCSInstaller was returned vcsInstaller, ok := i.(*VCSInstaller) - if !ok { - t.Fatal("expected a VCSInstaller") - } + require.True(t, ok, "expected a VCSInstaller") // set the testRepo in the VCSInstaller vcsInstaller.Repo = repo - if err := Install(i); err != nil { - t.Fatal(err) - } - if repo.current != "0.1.1" { - t.Fatalf("expected version '0.1.1', got %q", repo.current) - } + require.NoError(t, Install(i)) + require.Equal(t, "0.1.1", repo.current, "expected version '0.1.1', got %q", repo.current) expectedPath := helmpath.DataPath("plugins", "helm-env") - if i.Path() != expectedPath { - t.Fatalf("expected path %q, got %q", expectedPath, i.Path()) - } + require.Equal(t, expectedPath, i.Path(), "expected path %q, got %q", expectedPath, i.Path()) // Install again to test plugin exists error - if err := Install(i); err == nil { - t.Fatal("expected error for plugin exists, got none") - } else if err.Error() != "plugin already exists" { - t.Fatalf("expected error for plugin exists, got (%v)", err) - } + require.EqualErrorf(t, Install(i), "plugin already exists", "expected error for plugin exists") // Testing FindSource method, expect error because plugin code is not a cloned repository - if _, err := FindSource(i.Path()); err == nil { - t.Fatal("expected error for inability to find plugin source, got none") - } else if err.Error() != "cannot get information about plugin source" { - t.Fatalf("expected error for inability to find plugin source, got (%v)", err) - } + _, err = FindSource(i.Path()) + require.Error(t, err, "expected error for inability to find plugin source, got none") + require.EqualErrorf(t, err, "cannot get information about plugin source", "expected error for inability to find plugin source") } func TestVCSInstallerNonExistentVersion(t *testing.T) { @@ -110,21 +93,18 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) { version := "0.2.0" i, err := NewForSource(source, version) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } + require.NoError(t, err) // ensure a VCSInstaller was returned - if _, ok := i.(*VCSInstaller); !ok { - t.Fatal("expected a VCSInstaller") - } + _, ok := i.(*VCSInstaller) + require.True(t, ok, "expected a VCSInstaller") if err := Install(i); err == nil { t.Fatal("expected error for version does not exists, got none") } else if strings.Contains(err.Error(), "Could not resolve host: github.com") { t.Skip("Unable to run test without Internet access") - } else if err.Error() != fmt.Sprintf("requested version %q does not exist for plugin %q", version, source) { - t.Fatalf("expected error for version does not exists, got (%v)", err) + } else { + require.EqualErrorf(t, err, fmt.Sprintf("requested version %q does not exist for plugin %q", version, source), "expected error for version does not exists") } } func TestVCSInstallerUpdate(t *testing.T) { @@ -133,20 +113,13 @@ func TestVCSInstallerUpdate(t *testing.T) { source := "https://github.com/adamreese/helm-env" i, err := NewForSource(source, "") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } + require.NoError(t, err) // ensure a VCSInstaller was returned - if _, ok := i.(*VCSInstaller); !ok { - t.Fatal("expected a VCSInstaller") - } + _, ok := i.(*VCSInstaller) + require.True(t, ok, "expected a VCSInstaller") - if err := Update(i); err == nil { - t.Fatal("expected error for plugin does not exist, got none") - } else if err.Error() != "plugin does not exist" { - t.Fatalf("expected error for plugin does not exist, got (%v)", err) - } + require.EqualErrorf(t, Update(i), "plugin does not exist", "expected error for plugin does not exist") // Install plugin before update if err := Install(i); err != nil { @@ -159,30 +132,18 @@ func TestVCSInstallerUpdate(t *testing.T) { // Test FindSource method for positive result pluginInfo, err := FindSource(i.Path()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) vcsInstaller := pluginInfo.(*VCSInstaller) repoRemote := vcsInstaller.Repo.Remote() - if repoRemote != source { - t.Fatalf("invalid source found, expected %q got %q", source, repoRemote) - } + require.Equal(t, source, repoRemote, "invalid source found, expected %q got %q", source, repoRemote) // Update plugin - if err := Update(i); err != nil { - t.Fatal(err) - } + require.NoError(t, Update(i)) // Test update failure - if err := os.Remove(filepath.Join(vcsInstaller.Repo.LocalPath(), "plugin.yaml")); err != nil { - t.Fatal(err) - } + require.NoError(t, os.Remove(filepath.Join(vcsInstaller.Repo.LocalPath(), "plugin.yaml"))) // Testing update for error - if err := Update(vcsInstaller); err == nil { - t.Fatal("expected error for plugin modified, got none") - } else if err.Error() != "plugin repo was modified" { - t.Fatalf("expected error for plugin modified, got (%v)", err) - } + require.EqualErrorf(t, Update(vcsInstaller), "plugin repo was modified", "expected error for plugin modified") } From f3402c1cd69b84d278af5a30551e376d727d27c1 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:57 +0200 Subject: [PATCH 452/571] chore(internal): refactor: convert tests to testify assert/require part 6 refactor: convert tests to testify assert/require in internal/plugin Signed-off-by: Matthieu MOREL --- .../chart/v3/lint/rules/deprecations_test.go | 2 +- internal/chart/v3/lint/rules/values_test.go | 5 ++-- internal/chart/v3/util/validate_name_test.go | 28 ++++++++++--------- internal/fileutil/fileutil_test.go | 12 +++----- internal/logging/logging_test.go | 6 ++-- internal/plugin/installer/base_test.go | 6 ++-- internal/plugin/loader_test.go | 8 ++---- internal/plugin/metadata_test.go | 10 +++---- internal/plugin/metadata_v1_test.go | 3 +- internal/plugin/plugin_test.go | 10 +++---- internal/release/v2/hook_test.go | 23 +++++++-------- internal/sympath/walk_test.go | 3 +- 12 files changed, 49 insertions(+), 67 deletions(-) diff --git a/internal/chart/v3/lint/rules/deprecations_test.go b/internal/chart/v3/lint/rules/deprecations_test.go index 920fdc31f..96462bc15 100644 --- a/internal/chart/v3/lint/rules/deprecations_test.go +++ b/internal/chart/v3/lint/rules/deprecations_test.go @@ -32,7 +32,7 @@ func TestValidateNoDeprecations(t *testing.T) { require.Error(t, err, "Expected deprecated extension to be flagged") var depErr deprecatedAPIError require.ErrorAs(t, err, &depErr, "Expected error to be of type deprecatedAPIError") - require.NotEmpty(t, depErr.Message, "Expected error message to be non-blank: %v", err) + require.NotEmpty(t, depErr.Message, "Expected error message to be non-blank") assert.NoError(t, validateNoDeprecations(&k8sYamlStruct{ APIVersion: "v1", Kind: "Pod", diff --git a/internal/chart/v3/lint/rules/values_test.go b/internal/chart/v3/lint/rules/values_test.go index c87607f2d..7f1177729 100644 --- a/internal/chart/v3/lint/rules/values_test.go +++ b/internal/chart/v3/lint/rules/values_test.go @@ -56,8 +56,7 @@ func TestValidateValuesYamlNotDirectory(t *testing.T) { _ = os.Mkdir(nonExistingValuesFilePath, os.ModePerm) defer os.Remove(nonExistingValuesFilePath) - err := validateValuesFileExistence(nonExistingValuesFilePath) - assert.Error(t, err, "validateValuesFileExistence to return a linter error, got no error") + assert.Error(t, validateValuesFileExistence(nonExistingValuesFilePath), "validateValuesFileExistence to return a linter error, got no error") } func TestValidateValuesFileWellFormed(t *testing.T) { @@ -150,7 +149,7 @@ func TestValidateValuesFile(t *testing.T) { require.NoError(t, err, "Failed validation") } else { require.Error(t, err, "expected values file to fail parsing") - assert.ErrorContains(t, err, tt.errorMessage, "Failed with unexpected error") + assert.ErrorContains(t, err, tt.errorMessage) } }) } diff --git a/internal/chart/v3/util/validate_name_test.go b/internal/chart/v3/util/validate_name_test.go index cfc62a0f7..3ea768db2 100644 --- a/internal/chart/v3/util/validate_name_test.go +++ b/internal/chart/v3/util/validate_name_test.go @@ -16,7 +16,11 @@ limitations under the License. package util -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" +) // TestValidateReleaseName is a regression test for ValidateName // @@ -46,12 +50,11 @@ func TestValidateReleaseName(t *testing.T) { "a1111111111111111111111111111111111111111111111111111111111z": false, } for input, expectPass := range names { - if err := ValidateReleaseName(input); (err == nil) != expectPass { - st := "fail" - if expectPass { - st = "succeed" - } - t.Errorf("Expected %q to %s", input, st) + err := ValidateReleaseName(input) + if expectPass { + assert.NoErrorf(t, err, "Expected release name %q to pass validation", input) + } else { + assert.Errorf(t, err, "Expected release name %q to fail validation, but it passed", input) } } } @@ -80,12 +83,11 @@ func TestValidateMetadataName(t *testing.T) { "a1111111111111111111111111111111111111111111111111111111111z": false, } for input, expectPass := range names { - if err := ValidateMetadataName(input); (err == nil) != expectPass { - st := "fail" - if expectPass { - st = "succeed" - } - t.Errorf("Expected %q to %s", input, st) + err := ValidateMetadataName(input) + if expectPass { + assert.NoError(t, err, "Expected %q to succeed", input) + } else { + assert.Error(t, err, "Expected %q to fail", input) } } } diff --git a/internal/fileutil/fileutil_test.go b/internal/fileutil/fileutil_test.go index 9c997fcd3..85dd51894 100644 --- a/internal/fileutil/fileutil_test.go +++ b/internal/fileutil/fileutil_test.go @@ -37,8 +37,7 @@ func TestAtomicWriteFile(t *testing.T) { reader := bytes.NewReader([]byte(stringContent)) mode := os.FileMode(0o644) - err := AtomicWriteFile(testpath, reader, mode) - require.NoError(t, err) + require.NoError(t, AtomicWriteFile(testpath, reader, mode)) got, err := os.ReadFile(testpath) require.NoError(t, err) @@ -58,8 +57,7 @@ func TestAtomicWriteFile_CreateTempError(t *testing.T) { reader := bytes.NewReader([]byte("test content")) mode := os.FileMode(0o644) - err := AtomicWriteFile(invalidPath, reader, mode) - assert.Error(t, err, "Expected error when CreateTemp fails") + assert.Error(t, AtomicWriteFile(invalidPath, reader, mode), "Expected error when CreateTemp fails") } // TestAtomicWriteFile_EmptyContent tests with empty content @@ -70,8 +68,7 @@ func TestAtomicWriteFile_EmptyContent(t *testing.T) { reader := bytes.NewReader([]byte("")) mode := os.FileMode(0o644) - err := AtomicWriteFile(testpath, reader, mode) - require.NoError(t, err, "AtomicWriteFile error with empty content") + require.NoError(t, AtomicWriteFile(testpath, reader, mode), "AtomicWriteFile error with empty content") got, err := os.ReadFile(testpath) require.NoError(t, err) @@ -89,8 +86,7 @@ func TestAtomicWriteFile_LargeContent(t *testing.T) { reader := bytes.NewReader([]byte(largeContent)) mode := os.FileMode(0o644) - err := AtomicWriteFile(testpath, reader, mode) - require.NoError(t, err, "AtomicWriteFile error with large content") + require.NoError(t, AtomicWriteFile(testpath, reader, mode), "AtomicWriteFile error with large content") got, err := os.ReadFile(testpath) require.NoError(t, err) diff --git a/internal/logging/logging_test.go b/internal/logging/logging_test.go index 83979e9db..1d6eaf852 100644 --- a/internal/logging/logging_test.go +++ b/internal/logging/logging_test.go @@ -197,9 +197,8 @@ func TestDebugCheckHandler_Handle(t *testing.T) { } record := slog.NewRecord(time.Now(), slog.LevelInfo, "test message", 0) - err := handler.Handle(t.Context(), record) - require.NoError(t, err) + require.NoError(t, handler.Handle(t.Context(), record)) assert.Contains(t, buf.String(), "test message") }) @@ -214,9 +213,8 @@ func TestDebugCheckHandler_Handle(t *testing.T) { type testKey string ctx := context.WithValue(t.Context(), testKey("test"), "value") record := slog.NewRecord(time.Now(), slog.LevelInfo, "context test", 0) - err := handler.Handle(ctx, record) - require.NoError(t, err) + require.NoError(t, handler.Handle(ctx, record)) assert.Contains(t, buf.String(), "context test") }) } diff --git a/internal/plugin/installer/base_test.go b/internal/plugin/installer/base_test.go index b7902e699..8ef7ff588 100644 --- a/internal/plugin/installer/base_test.go +++ b/internal/plugin/installer/base_test.go @@ -15,6 +15,8 @@ package installer import ( "testing" + + "github.com/stretchr/testify/assert" ) func TestPath(t *testing.T) { @@ -38,8 +40,6 @@ func TestPath(t *testing.T) { t.Setenv("HELM_PLUGINS", tt.helmPluginsDir) baseIns := newBase(tt.source) baseInsPath := baseIns.Path() - if baseInsPath != tt.expectPath { - t.Errorf("expected name %s, got %s", tt.expectPath, baseInsPath) - } + assert.Equal(t, tt.expectPath, baseInsPath, "expected name %s, got %s", tt.expectPath, baseInsPath) } } diff --git a/internal/plugin/loader_test.go b/internal/plugin/loader_test.go index 4b98a6f83..e0682aed7 100644 --- a/internal/plugin/loader_test.go +++ b/internal/plugin/loader_test.go @@ -194,13 +194,9 @@ func TestDetectDuplicates(t *testing.T) { mockSubprocessCLIPlugin(t, "foo"), mockSubprocessCLIPlugin(t, "bar"), } - if err := detectDuplicates(plugs); err != nil { - t.Error("no duplicates in the first set") - } + require.NoError(t, detectDuplicates(plugs), "no duplicates in the first set") plugs = append(plugs, mockSubprocessCLIPlugin(t, "foo")) - if err := detectDuplicates(plugs); err == nil { - t.Error("duplicates in the second set") - } + assert.Error(t, detectDuplicates(plugs), "duplicates in the second set") } func TestLoadAllDir_Empty(t *testing.T) { diff --git a/internal/plugin/metadata_test.go b/internal/plugin/metadata_test.go index b4994cf68..9cfdbc83a 100644 --- a/internal/plugin/metadata_test.go +++ b/internal/plugin/metadata_test.go @@ -66,7 +66,7 @@ func TestValidatePluginData(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) { err := item.plug.Metadata().Validate() if item.pass { - require.NoError(t, err, "failed to validate case %d: %s", i, err) + require.NoError(t, err, "failed to validate case %d", i) } else { require.Error(t, err, "expected case %d to fail", i) assert.ErrorContains(t, err, item.errString, "expected case %d error to contain %q", i, item.errString) @@ -97,8 +97,7 @@ func TestMetadataValidateVersion(t *testing.T) { t.Run(name, func(t *testing.T) { m := mockSubprocessCLIPlugin(t, "testplugin") m.metadata.Version = tc.version - err := m.Metadata().Validate() - assert.NoError(t, err) + assert.NoError(t, m.Metadata().Validate()) }) } @@ -106,8 +105,7 @@ func TestMetadataValidateVersion(t *testing.T) { t.Run(name, func(t *testing.T) { m := mockSubprocessCLIPlugin(t, "testplugin") m.metadata.Version = tc.version - err := m.Metadata().Validate() - assert.ErrorContains(t, err, "invalid plugin version") + assert.ErrorContains(t, m.Metadata().Validate(), "invalid plugin version") }) } } @@ -139,7 +137,7 @@ func TestMetadataValidateMultipleErrors(t *testing.T) { } for _, expectedErr := range expectedErrors { - assert.Contains(t, errStr, expectedErr, "expected error to contain %q, but got: %v", expectedErr, errStr) + require.ErrorContains(t, err, expectedErr) } // Verify that the error contains the correct number of error messages diff --git a/internal/plugin/metadata_v1_test.go b/internal/plugin/metadata_v1_test.go index 74c67178e..8a6f28705 100644 --- a/internal/plugin/metadata_v1_test.go +++ b/internal/plugin/metadata_v1_test.go @@ -77,8 +77,7 @@ func TestMetadataV1ValidateVersion(t *testing.T) { t.Run("invalid/"+name, func(t *testing.T) { m := base() m.Version = tc.version - err := m.Validate() - assert.ErrorContains(t, err, tc.errMsg) + assert.ErrorContains(t, m.Validate(), tc.errMsg) }) } } diff --git a/internal/plugin/plugin_test.go b/internal/plugin/plugin_test.go index 2e3f274f2..947792046 100644 --- a/internal/plugin/plugin_test.go +++ b/internal/plugin/plugin_test.go @@ -18,6 +18,8 @@ package plugin import ( "testing" + "github.com/stretchr/testify/assert" + "helm.sh/helm/v4/internal/plugin/schema" ) @@ -34,9 +36,7 @@ func TestValidPluginName(t *testing.T) { for name, pluginName := range validNames { t.Run("valid/"+name, func(t *testing.T) { - if !validPluginName.MatchString(pluginName) { - t.Errorf("expected %q to match validPluginName regex", pluginName) - } + assert.Truef(t, validPluginName.MatchString(pluginName), "expected %q to match validPluginName regex", pluginName) }) } @@ -52,9 +52,7 @@ func TestValidPluginName(t *testing.T) { for name, pluginName := range invalidNames { t.Run("invalid/"+name, func(t *testing.T) { - if validPluginName.MatchString(pluginName) { - t.Errorf("expected %q to not match validPluginName regex", pluginName) - } + assert.Falsef(t, validPluginName.MatchString(pluginName), "expected %q to not match validPluginName regex", pluginName) }) } } diff --git a/internal/release/v2/hook_test.go b/internal/release/v2/hook_test.go index 5a0867398..3e946825e 100644 --- a/internal/release/v2/hook_test.go +++ b/internal/release/v2/hook_test.go @@ -170,12 +170,12 @@ func TestHookExecutionUnmarshalJSON(t *testing.T) { err := json.Unmarshal([]byte(tt.input), &exec) if tt.wantErr { assert.Error(t, err) - return + } else { + require.NoError(t, err) + assert.Equal(t, tt.expected.StartedAt.Unix(), exec.StartedAt.Unix()) + assert.Equal(t, tt.expected.CompletedAt.Unix(), exec.CompletedAt.Unix()) + assert.Equal(t, tt.expected.Phase, exec.Phase) } - require.NoError(t, err) - assert.Equal(t, tt.expected.StartedAt.Unix(), exec.StartedAt.Unix()) - assert.Equal(t, tt.expected.CompletedAt.Unix(), exec.CompletedAt.Unix()) - assert.Equal(t, tt.expected.Phase, exec.Phase) }) } } @@ -194,8 +194,7 @@ func TestHookExecutionRoundTrip(t *testing.T) { require.NoError(t, err) var decoded HookExecution - err = json.Unmarshal(data, &decoded) - require.NoError(t, err) + require.NoError(t, json.Unmarshal(data, &decoded)) assert.Equal(t, original.StartedAt.Unix(), decoded.StartedAt.Unix()) assert.Equal(t, original.CompletedAt.Unix(), decoded.CompletedAt.Unix()) @@ -208,12 +207,11 @@ func TestHookExecutionEmptyStringRoundTrip(t *testing.T) { input := `{"started_at":"","completed_at":"","phase":"Succeeded"}` var exec HookExecution - err := json.Unmarshal([]byte(input), &exec) - require.NoError(t, err) + require.NoError(t, json.Unmarshal([]byte(input), &exec)) // Verify time fields are zero values - assert.True(t, exec.StartedAt.IsZero()) - assert.True(t, exec.CompletedAt.IsZero()) + assert.Zero(t, exec.StartedAt) + assert.Zero(t, exec.CompletedAt) assert.Equal(t, HookPhaseSucceeded, exec.Phase) // Marshal back and verify empty time fields are omitted @@ -221,8 +219,7 @@ func TestHookExecutionEmptyStringRoundTrip(t *testing.T) { require.NoError(t, err) var result map[string]any - err = json.Unmarshal(data, &result) - require.NoError(t, err) + require.NoError(t, json.Unmarshal(data, &result)) // Zero time values should be omitted assert.NotContains(t, result, "started_at") diff --git a/internal/sympath/walk_test.go b/internal/sympath/walk_test.go index 76ed39cac..5dd051e4d 100644 --- a/internal/sympath/walk_test.go +++ b/internal/sympath/walk_test.go @@ -132,8 +132,7 @@ func TestWalk(t *testing.T) { return mark(info, err, &errors, true) } // Expect no errors. - err := Walk(tree.name, markFn) - require.NoError(t, err) + require.NoError(t, Walk(tree.name, markFn)) require.Empty(t, errors, "unexpected errors") checkMarks(t, true) From f1d5979e7d8b9d1e36b3cf703d150e8078c2e16e Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:56 +0200 Subject: [PATCH 453/571] chore(internal): refactor: convert tests to testify assert/require part 5 refactor: convert tests to testify assert/require in internal/plugin/installer Signed-off-by: Matthieu MOREL --- internal/chart/v3/lint/rules/template_test.go | 27 ++- .../chart/v3/lint/support/message_test.go | 29 +-- internal/chart/v3/util/chartfile_test.go | 27 +-- internal/plugin/installer/installer_test.go | 26 +-- .../plugin/installer/plugin_structure_test.go | 31 +-- .../plugin/installer/verification_test.go | 181 +++++------------- internal/plugin/runtime_extismv1_test.go | 3 +- internal/plugin/subprocess_commands_test.go | 28 +-- internal/plugin/verify_test.go | 26 +-- 9 files changed, 118 insertions(+), 260 deletions(-) diff --git a/internal/chart/v3/lint/rules/template_test.go b/internal/chart/v3/lint/rules/template_test.go index 0b83e1cd5..b4fa2d311 100644 --- a/internal/chart/v3/lint/rules/template_test.go +++ b/internal/chart/v3/lint/rules/template_test.go @@ -37,13 +37,11 @@ const templateTestBasedir = "./testdata/albatross" func TestValidateAllowedExtension(t *testing.T) { var failTest = []string{"/foo", "/test.toml"} for _, test := range failTest { - err := validateAllowedExtension(test) - require.ErrorContains(t, err, "Valid extensions are .yaml, .yml, .tpl, or .txt", "validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test) + require.ErrorContains(t, validateAllowedExtension(test), "Valid extensions are .yaml, .yml, .tpl, or .txt", "validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test) } var successTest = []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"} for _, test := range successTest { - err := validateAllowedExtension(test) - assert.NoError(t, err, "validateAllowedExtension('%s') to return no error", test) + assert.NoError(t, validateAllowedExtension(test), "validateAllowedExtension('%s') to return no error", test) } } @@ -58,8 +56,7 @@ func TestTemplateParsing(t *testing.T) { res := linter.Messages require.Len(t, res, 1, "Expected one error, got %d, %v", len(res), res) - - assert.ErrorContains(t, res[0].Err, "deliberateSyntaxError", "Unexpected error: %s", res[0]) + assert.ErrorContains(t, res[0].Err, "deliberateSyntaxError") } var wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml") @@ -86,7 +83,7 @@ func TestMultiTemplateFail(t *testing.T) { require.Len(t, res, 1, "Expected 1 error, got %d, %v", len(res), res) - assert.ErrorContains(t, res[0].Err, "object name does not conform to Kubernetes naming requirements", "Unexpected error: %s", res[0].Err) + assert.ErrorContains(t, res[0].Err, "object name does not conform to Kubernetes naming requirements") } func TestValidateMetadataName(t *testing.T) { @@ -292,8 +289,7 @@ spec: - name: nginx image: nginx:1.14.2 ` - err := validateMatchSelector(md, manifest) - require.NoError(t, err) + require.NoError(t, validateMatchSelector(md, manifest)) manifest = ` apiVersion: apps/v1 kind: Deployment @@ -315,8 +311,7 @@ spec: - name: nginx image: nginx:1.14.2 ` - err = validateMatchSelector(md, manifest) - require.NoError(t, err) + require.NoError(t, validateMatchSelector(md, manifest)) manifest = ` apiVersion: apps/v1 kind: Deployment @@ -335,8 +330,7 @@ spec: - name: nginx image: nginx:1.14.2 ` - err = validateMatchSelector(md, manifest) - assert.Error(t, err, "expected Deployment with no selector to fail") + assert.Error(t, validateMatchSelector(md, manifest), "expected Deployment with no selector to fail") } func TestValidateTopIndentLevel(t *testing.T) { @@ -349,8 +343,11 @@ func TestValidateTopIndentLevel(t *testing.T) { " apiVersion:foo": true, "\n\n apiVersion:foo\n\n": true, } { - if err := validateTopIndentLevel(doc); (err == nil) == shouldFail { - t.Errorf("Expected %t for %q", shouldFail, doc) + err := validateTopIndentLevel(doc) + if shouldFail { + assert.Errorf(t, err, "Expected %t for %q", shouldFail, doc) + } else { + assert.NoErrorf(t, err, "Expected %t for %q", shouldFail, doc) } } } diff --git a/internal/chart/v3/lint/support/message_test.go b/internal/chart/v3/lint/support/message_test.go index ce5b5e42e..4c3ed3a3f 100644 --- a/internal/chart/v3/lint/support/message_test.go +++ b/internal/chart/v3/lint/support/message_test.go @@ -19,6 +19,9 @@ package support import ( "errors" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var errLint = errors.New("lint failed") @@ -47,33 +50,19 @@ func TestRunLinterRule(t *testing.T) { linter := Linter{} for _, test := range tests { isValid := linter.RunLinterRule(test.Severity, "chart", test.LintError) - if len(linter.Messages) != test.ExpectedMessages { - t.Errorf("RunLinterRule(%d, \"chart\", %v), linter.Messages should now have %d message, we got %d", test.Severity, test.LintError, test.ExpectedMessages, len(linter.Messages)) - } - - if linter.HighestSeverity != test.ExpectedHighestSeverity { - t.Errorf("RunLinterRule(%d, \"chart\", %v), linter.HighestSeverity should be %d, we got %d", test.Severity, test.LintError, test.ExpectedHighestSeverity, linter.HighestSeverity) - } - - if isValid != test.ExpectedReturn { - t.Errorf("RunLinterRule(%d, \"chart\", %v), should have returned %t but returned %t", test.Severity, test.LintError, test.ExpectedReturn, isValid) - } + assert.Lenf(t, linter.Messages, test.ExpectedMessages, "RunLinterRule(%d, \"chart\", %v), linter.Messages should now have %d message, we got %d", test.Severity, test.LintError, test.ExpectedMessages, len(linter.Messages)) + assert.Equalf(t, test.ExpectedHighestSeverity, linter.HighestSeverity, "RunLinterRule(%d, \"chart\", %v), linter.HighestSeverity should be %d, we got %d", test.Severity, test.LintError, test.ExpectedHighestSeverity, linter.HighestSeverity) + assert.Equalf(t, test.ExpectedReturn, isValid, "RunLinterRule(%d, \"chart\", %v), should have returned %t but returned %t", test.Severity, test.LintError, test.ExpectedReturn, isValid) } } func TestMessage(t *testing.T) { m := Message{ErrorSev, "Chart.yaml", errors.New("Foo")} - if m.Error() != "[ERROR] Chart.yaml: Foo" { - t.Errorf("Unexpected output: %s", m.Error()) - } + require.EqualErrorf(t, m, "[ERROR] Chart.yaml: Foo", "Unexpected output: %s", m.Error()) m = Message{WarningSev, "templates/", errors.New("Bar")} - if m.Error() != "[WARNING] templates/: Bar" { - t.Errorf("Unexpected output: %s", m.Error()) - } + require.EqualErrorf(t, m, "[WARNING] templates/: Bar", "Unexpected output: %s", m.Error()) m = Message{InfoSev, "templates/rc.yaml", errors.New("FooBar")} - if m.Error() != "[INFO] templates/rc.yaml: FooBar" { - t.Errorf("Unexpected output: %s", m.Error()) - } + assert.EqualErrorf(t, m, "[INFO] templates/rc.yaml: FooBar", "Unexpected output: %s", m.Error()) } diff --git a/internal/chart/v3/util/chartfile_test.go b/internal/chart/v3/util/chartfile_test.go index c791930c1..0cc8a66af 100644 --- a/internal/chart/v3/util/chartfile_test.go +++ b/internal/chart/v3/util/chartfile_test.go @@ -29,10 +29,7 @@ const testfile = "testdata/chartfiletest.yaml" func TestLoadChartfile(t *testing.T) { f, err := LoadChartfile(testfile) - if err != nil { - t.Errorf("Failed to open %s: %s", testfile, err) - return - } + require.NoErrorf(t, err, "Failed to open %s", testfile) verifyChartfile(t, f, "frobnitz") } @@ -64,13 +61,11 @@ func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { require.Len(t, f.Annotations, 2, "Unexpected annotations") - if want, got := "extravalue", f.Annotations["extrakey"]; want != got { - t.Errorf("Want %q, but got %q", want, got) - } + want, got := "extravalue", f.Annotations["extrakey"] + assert.Equalf(t, want, got, "Want %q, but got %q", want, got) - if want, got := "anothervalue", f.Annotations["anotherkey"]; want != got { - t.Errorf("Want %q, but got %q", want, got) - } + want, got = "anothervalue", f.Annotations["anotherkey"] + assert.Equalf(t, want, got, "Want %q, but got %q", want, got) kk := []string{"frobnitz", "sprocket", "dodad"} for i, k := range f.Keywords { @@ -80,13 +75,9 @@ func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { func TestIsChartDir(t *testing.T) { validChartDir, err := IsChartDir("testdata/frobnitz") - if !validChartDir { - t.Errorf("unexpected error while reading chart-directory: (%v)", err) - return - } + require.NoError(t, err, "while reading chart-directory") + require.True(t, validChartDir, "expected valid chart directory") validChartDir, err = IsChartDir("testdata") - if validChartDir || err == nil { - t.Error("expected error but did not get any") - return - } + require.Error(t, err) + require.False(t, validChartDir, "expected invalid chart directory") } diff --git a/internal/plugin/installer/installer_test.go b/internal/plugin/installer/installer_test.go index 50e8e1ee2..620afc183 100644 --- a/internal/plugin/installer/installer_test.go +++ b/internal/plugin/installer/installer_test.go @@ -15,33 +15,27 @@ limitations under the License. package installer -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" +) func TestIsRemoteHTTPArchive(t *testing.T) { srv := mockArchiveServer() defer srv.Close() source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" - if isRemoteHTTPArchive("/not/a/URL") { - t.Error("Expected non-URL to return false") - } + assert.False(t, isRemoteHTTPArchive("/not/a/URL"), "Expected non-URL to return false") // URLs with valid archive extensions are considered valid archives // even if the server is unreachable (optimization to avoid unnecessary HTTP requests) - if !isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.tgz") { - t.Error("URL with .tgz extension should be considered a valid archive") - } + assert.True(t, isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.tgz"), "URL with .tgz extension should be considered a valid archive") // Test with invalid extension and unreachable server - if isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.notanarchive") { - t.Error("Bad URL without valid extension should not succeed") - } + assert.False(t, isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.notanarchive"), "Bad URL without valid extension should not succeed") - if !isRemoteHTTPArchive(source) { - t.Errorf("Expected %q to be a valid archive URL", source) - } + assert.True(t, isRemoteHTTPArchive(source), "Expected %q to be a valid archive URL", source) - if isRemoteHTTPArchive(source + "-not-an-extension") { - t.Error("Expected media type match to fail") - } + assert.False(t, isRemoteHTTPArchive(source+"-not-an-extension"), "Expected media type match to fail") } diff --git a/internal/plugin/installer/plugin_structure_test.go b/internal/plugin/installer/plugin_structure_test.go index 182eb9bd8..187918a4b 100644 --- a/internal/plugin/installer/plugin_structure_test.go +++ b/internal/plugin/installer/plugin_structure_test.go @@ -19,6 +19,9 @@ import ( "os" "path/filepath" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestDetectPluginRoot(t *testing.T) { @@ -73,26 +76,18 @@ func TestDetectPluginRoot(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dir := t.TempDir() - if err := tt.setup(dir); err != nil { - t.Fatalf("Setup failed: %v", err) - } + require.NoErrorf(t, tt.setup(dir), "Setup failed") root, err := detectPluginRoot(dir) if tt.expectError { - if err == nil { - t.Error("Expected error but got none") - } + assert.Error(t, err) } else { - if err != nil { - t.Errorf("Unexpected error: %v", err) - } + require.NoError(t, err) expectedPath := dir if tt.expectRoot != "." { expectedPath = filepath.Join(dir, tt.expectRoot) } - if root != expectedPath { - t.Errorf("Expected root %s but got %s", expectedPath, root) - } + assert.Equal(t, expectedPath, root, "Expected root %s but got %s", expectedPath, root) } }) } @@ -145,20 +140,14 @@ description: test` for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dir := t.TempDir() - if err := tt.setup(dir); err != nil { - t.Fatalf("Setup failed: %v", err) - } + require.NoErrorf(t, tt.setup(dir), "Setup failed") pluginRoot := filepath.Join(dir, tt.pluginRoot) err := validatePluginName(pluginRoot, tt.expectedName) if tt.expectError { - if err == nil { - t.Error("Expected error but got none") - } + assert.Error(t, err) } else { - if err != nil { - t.Errorf("Unexpected error: %v", err) - } + assert.NoError(t, err) } }) } diff --git a/internal/plugin/installer/verification_test.go b/internal/plugin/installer/verification_test.go index d3824a0fc..1f8aa7ef4 100644 --- a/internal/plugin/installer/verification_test.go +++ b/internal/plugin/installer/verification_test.go @@ -23,6 +23,9 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/internal/plugin" "helm.sh/helm/v4/internal/test/ensure" ) @@ -37,29 +40,20 @@ func TestInstallWithOptions_VerifyMissingProvenance(t *testing.T) { // Create local installer installer, err := NewLocalInstaller(pluginTgz) - if err != nil { - t.Fatalf("Failed to create installer: %v", err) - } + require.NoError(t, err, "Failed to create installer") defer os.RemoveAll(installer.Path()) // Install with verification enabled should fail when .prov is missing result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: "dummy"}) // Should fail with a missing provenance error - if err == nil { - t.Fatal("Expected installation to fail when .prov file is missing and verification is enabled") - } - if !strings.Contains(err.Error(), "no provenance file") { - t.Errorf("Expected 'no provenance file' in error message, got: %v", err) - } - if result != nil { - t.Errorf("Expected nil verification result when .prov file is missing, got: %+v", result) - } + require.Error(t, err, "Expected installation to fail when .prov file is missing and verification is enabled") + require.ErrorContains(t, err, "no provenance file") + assert.Nil(t, result, "Expected nil verification result when .prov file is missing, got: %+v", result) // Plugin should NOT be installed - if _, err := os.Stat(installer.Path()); !os.IsNotExist(err) { - t.Error("Plugin should not be installed when verification fails due to missing .prov") - } + _, err = os.Stat(installer.Path()) + assert.True(t, os.IsNotExist(err), "Plugin should not be installed when verification fails due to missing .prov") } func TestInstallWithOptions_NoVerifyMissingProvenance(t *testing.T) { @@ -72,25 +66,18 @@ func TestInstallWithOptions_NoVerifyMissingProvenance(t *testing.T) { // Create local installer installer, err := NewLocalInstaller(pluginTgz) - if err != nil { - t.Fatalf("Failed to create installer: %v", err) - } + require.NoError(t, err, "Failed to create installer") defer os.RemoveAll(installer.Path()) // Install with verification explicitly disabled should succeed without .prov result, err := InstallWithOptions(installer, Options{Verify: false}) - if err != nil { - t.Fatalf("Expected installation to succeed with --verify=false, got error: %v", err) - } - if result != nil { - t.Errorf("Expected nil verification result when verification is disabled, got: %+v", result) - } + require.NoError(t, err, "Expected installation to succeed with --verify=false") + assert.Nil(t, result, "Expected nil verification result when verification is disabled, got: %+v", result) // Plugin should be installed - if _, err := os.Stat(installer.Path()); os.IsNotExist(err) { - t.Errorf("Plugin should be installed at %s", installer.Path()) - } + _, err = os.Stat(installer.Path()) + assert.Falsef(t, os.IsNotExist(err), "Plugin should be installed at %s", installer.Path()) } func TestInstallWithOptions_VerifyWithValidProvenance(t *testing.T) { @@ -110,9 +97,7 @@ func TestInstallWithOptions_VerifyWithValidProvenance(t *testing.T) { // Create local installer installer, err := NewLocalInstaller(pluginTgz) - if err != nil { - t.Fatalf("Failed to create installer: %v", err) - } + require.NoError(t, err, "Failed to create installer") defer os.RemoveAll(installer.Path()) // Install with verification enabled @@ -120,20 +105,13 @@ func TestInstallWithOptions_VerifyWithValidProvenance(t *testing.T) { result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: keyring}) // Should fail due to invalid signature (empty keyring) but we test that it gets past the hash check - if err == nil { - t.Fatal("Expected installation to fail with empty keyring") - } - if !strings.Contains(err.Error(), "plugin verification failed") { - t.Errorf("Expected plugin verification failed error, got: %v", err) - } - if result != nil { - t.Errorf("Expected nil verification result when verification fails, got: %+v", result) - } + require.Error(t, err, "Expected installation to fail with empty keyring") + require.ErrorContains(t, err, "plugin verification failed") + assert.Nil(t, result, "Expected nil verification result when verification fails, got: %+v", result) // Plugin should not be installed due to verification failure - if _, err := os.Stat(installer.Path()); !os.IsNotExist(err) { - t.Error("Plugin should not be installed when verification fails") - } + _, err = os.Stat(installer.Path()) + assert.True(t, os.IsNotExist(err), "Plugin should not be installed when verification fails") } func TestInstallWithOptions_VerifyWithInvalidProvenance(t *testing.T) { @@ -154,32 +132,23 @@ func TestInstallWithOptions_VerifyWithInvalidProvenance(t *testing.T) { // Create local installer installer, err := NewLocalInstaller(pluginTgz) - if err != nil { - t.Fatalf("Failed to create installer: %v", err) - } + require.NoError(t, err, "Failed to create installer") defer os.RemoveAll(installer.Path()) // Install with verification enabled (should fail) result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: keyring}) // Should fail with verification error - if err == nil { - t.Fatal("Expected installation with invalid .prov file to fail") - } - if result != nil { - t.Errorf("Expected nil verification result when verification fails, got: %+v", result) - } + require.Error(t, err, "Expected installation with invalid .prov file to fail") + assert.Nil(t, result, "Expected nil verification result when verification fails, got: %+v", result) // Should contain verification failure message expectedError := "plugin verification failed" - if !strings.Contains(err.Error(), expectedError) { - t.Errorf("Expected error message '%s', got: %s", expectedError, err.Error()) - } + require.ErrorContains(t, err, expectedError) // Plugin should not be installed - if _, err := os.Stat(installer.Path()); !os.IsNotExist(err) { - t.Error("Plugin should not be installed when verification fails") - } + _, err = os.Stat(installer.Path()) + assert.True(t, os.IsNotExist(err), "Plugin should not be installed when verification fails") } func TestInstallWithOptions_NoVerifyRequested(t *testing.T) { @@ -192,26 +161,19 @@ func TestInstallWithOptions_NoVerifyRequested(t *testing.T) { // Create local installer installer, err := NewLocalInstaller(pluginTgz) - if err != nil { - t.Fatalf("Failed to create installer: %v", err) - } + require.NoError(t, err, "Failed to create installer") defer os.RemoveAll(installer.Path()) // Install without verification (should succeed without any verification) result, err := InstallWithOptions(installer, Options{Verify: false}) // Should succeed with no verification - if err != nil { - t.Fatalf("Expected installation without verification to succeed, got error: %v", err) - } - if result != nil { - t.Errorf("Expected nil verification result when verification is disabled, got: %+v", result) - } + require.NoError(t, err, "Expected installation without verification to succeed") + assert.Nil(t, result, "Expected nil verification result when verification is disabled, got: %+v", result) // Plugin should be installed - if _, err := os.Stat(installer.Path()); os.IsNotExist(err) { - t.Errorf("Plugin should be installed at %s", installer.Path()) - } + _, err = os.Stat(installer.Path()) + assert.Falsef(t, os.IsNotExist(err), "Plugin should be installed at %s", installer.Path()) } func TestInstallWithOptions_VerifyDirectoryNotSupported(t *testing.T) { @@ -222,24 +184,16 @@ func TestInstallWithOptions_VerifyDirectoryNotSupported(t *testing.T) { // Create local installer for directory installer, err := NewLocalInstaller(pluginDir) - if err != nil { - t.Fatalf("Failed to create installer: %v", err) - } + require.NoError(t, err, "Failed to create installer") defer os.RemoveAll(installer.Path()) // Install with verification should fail (directories don't support verification) result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: "dummy"}) // Should fail with verification not supported error - if err == nil { - t.Fatal("Expected installation to fail with verification not supported error") - } - if !strings.Contains(err.Error(), "--verify is only supported for plugin tarballs") { - t.Errorf("Expected verification not supported error, got: %v", err) - } - if result != nil { - t.Errorf("Expected nil verification result when verification fails, got: %+v", result) - } + require.Error(t, err, "Expected installation to fail with verification not supported error") + require.ErrorContains(t, err, "--verify is only supported for plugin tarballs") + assert.Nil(t, result, "Expected nil verification result when verification fails, got: %+v", result) } func TestInstallWithOptions_VerifyMismatchedProvenance(t *testing.T) { @@ -261,24 +215,16 @@ func TestInstallWithOptions_VerifyMismatchedProvenance(t *testing.T) { // Create local installer installer, err := NewLocalInstaller(pluginTgz) - if err != nil { - t.Fatalf("Failed to create installer: %v", err) - } + require.NoError(t, err, "Failed to create installer") defer os.RemoveAll(installer.Path()) // Install with verification should fail due to hash mismatch result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: keyring}) // Should fail with verification error - if err == nil { - t.Fatal("Expected installation to fail with hash mismatch") - } - if !strings.Contains(err.Error(), "plugin verification failed") { - t.Errorf("Expected plugin verification failed error, got: %v", err) - } - if result != nil { - t.Errorf("Expected nil verification result when verification fails, got: %+v", result) - } + require.Error(t, err, "Expected installation to fail with hash mismatch") + require.ErrorContains(t, err, "plugin verification failed") + assert.Nil(t, result, "Expected nil verification result when verification fails, got: %+v", result) } func TestInstallWithOptions_VerifyProvenanceAccessError(t *testing.T) { @@ -291,9 +237,7 @@ func TestInstallWithOptions_VerifyProvenanceAccessError(t *testing.T) { // Create a .prov file but make it inaccessible (simulate permission error) provFile := pluginTgz + ".prov" - if err := os.WriteFile(provFile, []byte("test"), 0o000); err != nil { - t.Fatalf("Failed to create inaccessible provenance file: %v", err) - } + require.NoErrorf(t, os.WriteFile(provFile, []byte("test"), 0o000), "Failed to create inaccessible provenance file") defer os.Remove(provFile) // Create keyring @@ -302,27 +246,21 @@ func TestInstallWithOptions_VerifyProvenanceAccessError(t *testing.T) { // Create local installer installer, err := NewLocalInstaller(pluginTgz) - if err != nil { - t.Fatalf("Failed to create installer: %v", err) - } + require.NoError(t, err, "Failed to create installer") defer os.RemoveAll(installer.Path()) // Install with verification should fail due to access error result, err := InstallWithOptions(installer, Options{Verify: true, Keyring: keyring}) // Should fail with access error (either at stat level or during verification) - if err == nil { - t.Fatal("Expected installation to fail with provenance file access error") - } + require.Error(t, err, "Expected installation to fail with provenance file access error") // The error could be either "failed to access provenance file" or "plugin verification failed" // depending on when the permission error occurs if !strings.Contains(err.Error(), "failed to access provenance file") && !strings.Contains(err.Error(), "plugin verification failed") { t.Errorf("Expected provenance file access or verification error, got: %v", err) } - if result != nil { - t.Errorf("Expected nil verification result when verification fails, got: %+v", result) - } + assert.Nil(t, result, "Expected nil verification result when verification fails, got: %+v", result) } // Helper functions for test setup @@ -333,9 +271,7 @@ func createTestPluginDir(t *testing.T) string { // Create temporary directory with plugin structure tmpDir := t.TempDir() pluginDir := filepath.Join(tmpDir, "test-plugin") - if err := os.MkdirAll(pluginDir, 0o755); err != nil { - t.Fatalf("Failed to create plugin directory: %v", err) - } + require.NoErrorf(t, os.MkdirAll(pluginDir, 0o755), "Failed to create plugin directory") // Create plugin.yaml using the standardized v1 format pluginYaml := `apiVersion: v1 @@ -346,9 +282,7 @@ version: 1.0.0 runtimeConfig: platformCommand: - command: echo` - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginYaml), 0o644); err != nil { - t.Fatalf("Failed to create plugin.yaml: %v", err) - } + require.NoErrorf(t, os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginYaml), 0o644), "Failed to create plugin.yaml") return pluginDir } @@ -360,15 +294,10 @@ func createTarballFromPluginDir(t *testing.T, pluginDir string) string { tmpDir := filepath.Dir(pluginDir) tgzPath := filepath.Join(tmpDir, "test-plugin-1.0.0.tgz") tarFile, err := os.Create(tgzPath) - if err != nil { - t.Fatalf("Failed to create tarball file: %v", err) - } + require.NoError(t, err, "Failed to create tarball file") defer tarFile.Close() - if err := plugin.CreatePluginTarball(pluginDir, "test-plugin", tarFile); err != nil { - t.Fatalf("Failed to create tarball: %v", err) - } - + require.NoErrorf(t, plugin.CreatePluginTarball(pluginDir, "test-plugin", tarFile), "Failed to create tarball") return tgzPath } @@ -379,9 +308,7 @@ func createProvFile(t *testing.T, provFile, pluginTgz, hash string) { if hash == "" { // Calculate actual hash of the tarball for realistic testing data, err := os.ReadFile(pluginTgz) - if err != nil { - t.Fatalf("Failed to read tarball for hashing: %v", err) - } + require.NoError(t, err, "Failed to read tarball for hashing") hashSum := sha256.Sum256(data) hashStr = fmt.Sprintf("sha256:%x", hashSum) } else { @@ -404,9 +331,7 @@ Version: GnuPG v1 iQEcBAEBCAAGBQJktest... -----END PGP SIGNATURE----- `, hashStr) - if err := os.WriteFile(provFile, []byte(provContent), 0o644); err != nil { - t.Fatalf("Failed to create provenance file: %v", err) - } + require.NoErrorf(t, os.WriteFile(provFile, []byte(provContent), 0o644), "Failed to create provenance file") } func createProvFileInvalidFormat(t *testing.T, provFile string) { @@ -414,9 +339,7 @@ func createProvFileInvalidFormat(t *testing.T, provFile string) { // Create an invalid provenance file (not PGP signed format) invalidProv := "This is not a valid PGP signed message" - if err := os.WriteFile(provFile, []byte(invalidProv), 0o644); err != nil { - t.Fatalf("Failed to create invalid provenance file: %v", err) - } + require.NoErrorf(t, os.WriteFile(provFile, []byte(invalidProv), 0o644), "Failed to create invalid provenance file") } func createTestKeyring(t *testing.T) string { @@ -427,9 +350,7 @@ func createTestKeyring(t *testing.T) string { keyringPath := filepath.Join(tmpDir, "pubring.gpg") // Create empty keyring for testing - if err := os.WriteFile(keyringPath, []byte{}, 0o644); err != nil { - t.Fatalf("Failed to create test keyring: %v", err) - } + require.NoErrorf(t, os.WriteFile(keyringPath, []byte{}, 0o644), "Failed to create test keyring") return keyringPath } diff --git a/internal/plugin/runtime_extismv1_test.go b/internal/plugin/runtime_extismv1_test.go index 290d418ea..72a24d111 100644 --- a/internal/plugin/runtime_extismv1_test.go +++ b/internal/plugin/runtime_extismv1_test.go @@ -59,8 +59,7 @@ func buildLoadExtismPlugin(t *testing.T, dir string) pluginRaw { func TestRuntimeConfigExtismV1Validate(t *testing.T) { rc := RuntimeConfigExtismV1{} - err := rc.Validate() - assert.NoError(t, err, "expected no error for empty RuntimeConfigExtismV1") + assert.NoError(t, rc.Validate(), "expected no error for empty RuntimeConfigExtismV1") } func TestRuntimeExtismV1InvokePlugin(t *testing.T) { diff --git a/internal/plugin/subprocess_commands_test.go b/internal/plugin/subprocess_commands_test.go index 6aa0a7435..b2ac19c02 100644 --- a/internal/plugin/subprocess_commands_test.go +++ b/internal/plugin/subprocess_commands_test.go @@ -39,9 +39,7 @@ func TestPrepareCommand(t *testing.T) { cmd, args, err := PrepareCommands(platformCommand, true, []string{}, env) require.NoError(t, err) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) - if !reflect.DeepEqual(args, cmdArgs) { - t.Fatalf("Expected %v, got %v", cmdArgs, args) - } + require.Truef(t, reflect.DeepEqual(args, cmdArgs), "Expected %v, got %v", cmdArgs, args) } func TestPrepareCommandExtraArgs(t *testing.T) { @@ -113,9 +111,7 @@ func TestPrepareCommands(t *testing.T) { cmd, args, err := PrepareCommands(cmds, true, []string{}, env) require.NoError(t, err) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) - if !reflect.DeepEqual(args, cmdArgs) { - t.Fatalf("Expected %v, got %v", cmdArgs, args) - } + require.Truef(t, reflect.DeepEqual(args, cmdArgs), "Expected %v, got %v", cmdArgs, args) } func TestPrepareCommandsExtraArgs(t *testing.T) { @@ -136,9 +132,7 @@ func TestPrepareCommandsExtraArgs(t *testing.T) { cmd, args, err := PrepareCommands(cmds, true, extraArgs, env) require.NoError(t, err) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) - if !reflect.DeepEqual(args, expectedArgs) { - t.Fatalf("Expected %v, got %v", expectedArgs, args) - } + require.Truef(t, reflect.DeepEqual(args, expectedArgs), "Expected %v, got %v", expectedArgs, args) } func TestPrepareCommandsNoArch(t *testing.T) { @@ -155,9 +149,7 @@ func TestPrepareCommandsNoArch(t *testing.T) { cmd, args, err := PrepareCommands(cmds, true, []string{}, env) require.NoError(t, err) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) - if !reflect.DeepEqual(args, cmdArgs) { - t.Fatalf("Expected %v, got %v", cmdArgs, args) - } + require.Truef(t, reflect.DeepEqual(args, cmdArgs), "Expected %v, got %v", cmdArgs, args) } func TestPrepareCommandsNoOsNoArch(t *testing.T) { @@ -174,9 +166,7 @@ func TestPrepareCommandsNoOsNoArch(t *testing.T) { cmd, args, err := PrepareCommands(cmds, true, []string{}, env) require.NoError(t, err) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) - if !reflect.DeepEqual(args, cmdArgs) { - t.Fatalf("Expected %v, got %v", cmdArgs, args) - } + require.Truef(t, reflect.DeepEqual(args, cmdArgs), "Expected %v, got %v", cmdArgs, args) } func TestPrepareCommandsNoMatch(t *testing.T) { @@ -216,9 +206,7 @@ func TestPrepareCommandsExpand(t *testing.T) { cmd, args, err := PrepareCommands(cmds, true, []string{}, env) require.NoError(t, err) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) - if !reflect.DeepEqual(args, expectedArgs) { - t.Fatalf("Expected %v, got %v", expectedArgs, args) - } + require.Truef(t, reflect.DeepEqual(args, expectedArgs), "Expected %v, got %v", expectedArgs, args) } func TestPrepareCommandsNoExpand(t *testing.T) { @@ -235,7 +223,5 @@ func TestPrepareCommandsNoExpand(t *testing.T) { cmd, args, err := PrepareCommands(cmds, false, []string{}, env) require.NoError(t, err) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) - if !reflect.DeepEqual(args, cmdArgs) { - t.Fatalf("Expected %v, got %v", cmdArgs, args) - } + require.Truef(t, reflect.DeepEqual(args, cmdArgs), "Expected %v, got %v", cmdArgs, args) } diff --git a/internal/plugin/verify_test.go b/internal/plugin/verify_test.go index 5721e0414..4f5b26a97 100644 --- a/internal/plugin/verify_test.go +++ b/internal/plugin/verify_test.go @@ -44,11 +44,9 @@ func TestVerifyPlugin(t *testing.T) { // Create plugin directory pluginDir := filepath.Join(tempDir, "verify-test-plugin") - err := os.MkdirAll(pluginDir, 0o755) - require.NoError(t, err) + require.NoError(t, os.MkdirAll(pluginDir, 0o755)) - err = os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644) - require.NoError(t, err) + require.NoError(t, os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644)) // Create tarball tarballPath := filepath.Join(tempDir, "verify-test-plugin.tar.gz") @@ -64,10 +62,9 @@ func TestVerifyPlugin(t *testing.T) { // Sign the plugin with source directory signer, err := provenance.NewFromKeyring(testKeyFile, "helm-test") require.NoError(t, err) - err = signer.DecryptKey(func(_ string) ([]byte, error) { + require.NoError(t, signer.DecryptKey(func(_ string) ([]byte, error) { return []byte(""), nil - }) - require.NoError(t, err) + })) // Read the tarball data tarballData, err := os.ReadFile(tarballPath) @@ -78,8 +75,7 @@ func TestVerifyPlugin(t *testing.T) { // Write the signature to .prov file provFile := tarballPath + ".prov" - err = os.WriteFile(provFile, []byte(sig), 0o644) - require.NoError(t, err) + require.NoError(t, os.WriteFile(provFile, []byte(sig), 0o644)) // Read the files for verification archiveData, err := os.ReadFile(tarballPath) @@ -105,11 +101,9 @@ func TestVerifyPluginBadSignature(t *testing.T) { // Create a plugin tarball pluginDir := filepath.Join(tempDir, "bad-plugin") - err := os.MkdirAll(pluginDir, 0o755) - require.NoError(t, err) + require.NoError(t, os.MkdirAll(pluginDir, 0o755)) - err = os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644) - require.NoError(t, err) + require.NoError(t, os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644)) tarballPath := filepath.Join(tempDir, "bad-plugin.tar.gz") tarFile, err := os.Create(tarballPath) @@ -133,8 +127,7 @@ InvalidSignatureData -----END PGP SIGNATURE-----` provFile := tarballPath + ".prov" - err = os.WriteFile(provFile, []byte(badSig), 0o644) - require.NoError(t, err) + require.NoError(t, os.WriteFile(provFile, []byte(badSig), 0o644)) // Read the files archiveData, err := os.ReadFile(tarballPath) @@ -153,8 +146,7 @@ func TestVerifyPluginMissingProvenance(t *testing.T) { tarballPath := filepath.Join(tempDir, "no-prov.tar.gz") // Create a minimal tarball - err := os.WriteFile(tarballPath, []byte("dummy"), 0o644) - require.NoError(t, err) + require.NoError(t, os.WriteFile(tarballPath, []byte("dummy"), 0o644)) // Read the tarball data archiveData, err := os.ReadFile(tarballPath) From 668e7e580a01dbf66da0f620da6b980ee0cb4d80 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:57 +0200 Subject: [PATCH 454/571] chore(internal): refactor: convert tests to testify assert/require part 3 refactor: convert tests to testify assert/require in internal Signed-off-by: Matthieu MOREL --- internal/chart/v3/dependency_test.go | 10 +- .../chart/v3/lint/rules/chartfile_test.go | 53 ++-- internal/chart/v3/util/expand_test.go | 40 +-- internal/third_party/dep/fs/fs_test.go | 262 +++++------------- 4 files changed, 112 insertions(+), 253 deletions(-) diff --git a/internal/chart/v3/dependency_test.go b/internal/chart/v3/dependency_test.go index fcea19aea..54b9ed9da 100644 --- a/internal/chart/v3/dependency_test.go +++ b/internal/chart/v3/dependency_test.go @@ -17,6 +17,8 @@ package v3 import ( "testing" + + "github.com/stretchr/testify/assert" ) func TestValidateDependency(t *testing.T) { @@ -35,10 +37,10 @@ func TestValidateDependency(t *testing.T) { } { dep.Alias = value res := dep.Validate() - if res != nil && !shouldFail { - t.Errorf("Failed on case %q", dep.Alias) - } else if res == nil && shouldFail { - t.Errorf("Expected failure for %q", dep.Alias) + if shouldFail { + assert.Errorf(t, res, "Expected failure for %q", dep.Alias) + } else { + assert.NoErrorf(t, res, "Failed on case %q", dep.Alias) } } } diff --git a/internal/chart/v3/lint/rules/chartfile_test.go b/internal/chart/v3/lint/rules/chartfile_test.go index 9ac4ce75e..0638589b6 100644 --- a/internal/chart/v3/lint/rules/chartfile_test.go +++ b/internal/chart/v3/lint/rules/chartfile_test.go @@ -52,24 +52,19 @@ func TestValidateChartYamlNotDirectory(t *testing.T) { _ = os.Mkdir(nonExistingChartFilePath, os.ModePerm) defer os.Remove(nonExistingChartFilePath) - err := validateChartYamlNotDirectory(nonExistingChartFilePath) - assert.Error(t, err, "validateChartYamlNotDirectory to return a linter error, got no error") + assert.Error(t, validateChartYamlNotDirectory(nonExistingChartFilePath), "validateChartYamlNotDirectory to return a linter error, got no error") } func TestValidateChartYamlFormat(t *testing.T) { - err := validateChartYamlFormat(errors.New("Read error")) - require.Error(t, err, "validateChartYamlFormat to return a linter error, got no error") + require.Error(t, validateChartYamlFormat(errors.New("Read error")), "validateChartYamlFormat to return a linter error, got no error") - err = validateChartYamlFormat(nil) - assert.NoError(t, err, "validateChartYamlFormat to return no error, got a linter error") + assert.NoError(t, validateChartYamlFormat(nil), "validateChartYamlFormat to return no error, got a linter error") } func TestValidateChartName(t *testing.T) { - err := validateChartName(badChart) - require.Error(t, err, "validateChartName to return a linter error, got no error") + require.Error(t, validateChartName(badChart), "validateChartName to return a linter error, got no error") - err = validateChartName(badChartName) - assert.Error(t, err, "expected validateChartName to return a linter error for an invalid name, got no error") + assert.Error(t, validateChartName(badChartName), "expected validateChartName to return a linter error for an invalid name, got no error") } func TestValidateChartVersion(t *testing.T) { @@ -90,15 +85,13 @@ func TestValidateChartVersion(t *testing.T) { for i, test := range failTest { t.Run(strconv.Itoa(i), func(t *testing.T) { badChart.Version = test.Version - err := validateChartVersion(badChart) - require.ErrorContains(t, err, test.ErrorMsg, "validateChartVersion(%s) to return \"%s\", got no error", test.Version, test.ErrorMsg) + require.ErrorContains(t, validateChartVersion(badChart), test.ErrorMsg, "validateChartVersion(%s) to return \"%s\", got no error", test.Version, test.ErrorMsg) }) } for _, version := range successTest { badChart.Version = version - err := validateChartVersion(badChart) - assert.NoError(t, err, "validateChartVersion(%s) to return no error, got a linter error", version) + assert.NoError(t, validateChartVersion(badChart), "validateChartVersion(%s) to return no error, got a linter error", version) } } @@ -124,24 +117,20 @@ func TestValidateChartMaintainer(t *testing.T) { for _, test := range failTest { t.Run(fmt.Sprintf("%s, %s", test.Name, test.Email), func(t *testing.T) { badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}} - err := validateChartMaintainer(badChart) - require.ErrorContains(t, err, test.ErrorMsg, "validateChartMaintainer(%s, %s) to return \"%s\", got no error", test.Name, test.Email, test.ErrorMsg) + require.ErrorContains(t, validateChartMaintainer(badChart), test.ErrorMsg, "validateChartMaintainer(%s, %s) to return \"%s\", got no error", test.Name, test.Email, test.ErrorMsg) }) } for _, test := range successTest { t.Run(fmt.Sprintf("%s, %s", test.Name, test.Email), func(t *testing.T) { badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}} - err := validateChartMaintainer(badChart) - require.NoError(t, err, "validateChartMaintainer(%s, %s) to return no error", test.Name, test.Email) + require.NoError(t, validateChartMaintainer(badChart), "validateChartMaintainer(%s, %s) to return no error", test.Name, test.Email) }) } // Testing for an empty maintainer badChart.Maintainers = []*chart.Maintainer{nil} - err := validateChartMaintainer(badChart) - require.Error(t, err, "validateChartMaintainer did not return error for nil maintainer as expected") - assert.EqualError(t, err, "a maintainer entry is empty", "validateChartMaintainer returned unexpected error for nil maintainer") + assert.EqualError(t, validateChartMaintainer(badChart), "a maintainer entry is empty") } func TestValidateChartSources(t *testing.T) { @@ -150,15 +139,13 @@ func TestValidateChartSources(t *testing.T) { for _, test := range failTest { t.Run(test, func(t *testing.T) { badChart.Sources = []string{test} - err := validateChartSources(badChart) - require.ErrorContains(t, err, "invalid source URL", "validateChartSources(%s) to return \"invalid source URL\", got no error", test) + require.ErrorContains(t, validateChartSources(badChart), "invalid source URL", "validateChartSources(%s) to return \"invalid source URL\", got no error", test) }) } for _, test := range successTest { badChart.Sources = []string{test} - err := validateChartSources(badChart) - assert.NoError(t, err, "validateChartSources(%s) to return no error", test) + assert.NoError(t, validateChartSources(badChart), "validateChartSources(%s) to return no error", test) } } @@ -168,19 +155,13 @@ func TestValidateChartIconPresence(t *testing.T) { Icon: "", } - err := validateChartIconPresence(testChart) - - require.Error(t, err, "validateChartIconPresence to return a linter error, got no error") - assert.ErrorContains(t, err, "icon is recommended", "expected %q", "icon is recommended") + assert.ErrorContains(t, validateChartIconPresence(testChart), "icon is recommended", "expected %q", "icon is recommended") }) t.Run("Icon present", func(t *testing.T) { testChart := &chart.Metadata{ Icon: "http://example.org/icon.png", } - - err := validateChartIconPresence(testChart) - - assert.NoError(t, err, "Unexpected error") + assert.NoError(t, validateChartIconPresence(testChart)) }) } @@ -190,15 +171,13 @@ func TestValidateChartIconURL(t *testing.T) { for _, test := range failTest { t.Run(test, func(t *testing.T) { badChart.Icon = test - err := validateChartIconURL(badChart) - require.ErrorContains(t, err, "invalid icon URL", "validateChartIconURL(%s) to return \"invalid icon URL\", got no error", test) + require.ErrorContains(t, validateChartIconURL(badChart), "invalid icon URL", "validateChartIconURL(%s) to return \"invalid icon URL\", got no error", test) }) } for _, test := range successTest { badChart.Icon = test - err := validateChartIconURL(badChart) - assert.NoError(t, err, "validateChartIconURL(%s) to return no error", test) + assert.NoError(t, validateChartIconURL(badChart), "validateChartIconURL(%s) to return no error", test) } } diff --git a/internal/chart/v3/util/expand_test.go b/internal/chart/v3/util/expand_test.go index a7a756485..e6d66b74e 100644 --- a/internal/chart/v3/util/expand_test.go +++ b/internal/chart/v3/util/expand_test.go @@ -47,19 +47,19 @@ func makeTestChartArchive(t *testing.T, chartName, sourceDir string) *bytes.Buff fStat, err := f.Stat() require.NoError(t, err) - err = tw.WriteHeader(&tar.Header{ + require.NoError(t, tw.WriteHeader(&tar.Header{ Name: filepath.Join(chartName, relPath), Mode: int64(fStat.Mode()), Size: fStat.Size(), - }) - require.NoError(t, err) + })) data, err := fs.ReadFile(dir, relPath) require.NoError(t, err) - tw.Write(data) + _, err = tw.Write(data) + require.NoError(t, err) } - err := fs.WalkDir(dir, ".", func(path string, d os.DirEntry, walkErr error) error { + require.NoError(t, fs.WalkDir(dir, ".", func(path string, d os.DirEntry, walkErr error) error { if walkErr != nil { return walkErr } @@ -71,13 +71,10 @@ func makeTestChartArchive(t *testing.T, chartName, sourceDir string) *bytes.Buff writeFile(path) return nil - }) - require.NoError(t, err) + })) - err = tw.Close() - require.NoError(t, err) - err = gw.Close() - require.NoError(t, err) + require.NoError(t, tw.Close()) + require.NoError(t, gw.Close()) return &result } @@ -87,20 +84,18 @@ func TestExpand(t *testing.T) { reader, err := os.Open("testdata/frobnitz-1.2.3.tgz") require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, reader.Close()) }) - if err := Expand(dest, reader); err != nil { - t.Fatal(err) - } + require.NoError(t, Expand(dest, reader)) expectedChartPath := filepath.Join(dest, "frobnitz") fi, err := os.Stat(expectedChartPath) require.NoError(t, err) - if !fi.IsDir() { - t.Fatalf("expected a chart directory at %s", expectedChartPath) - } + require.Truef(t, fi.IsDir(), "expected a chart directory at %s", expectedChartPath) dir, err := os.Open(expectedChartPath) require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, dir.Close()) }) fis, err := dir.Readdir(0) require.NoError(t, err) @@ -137,8 +132,7 @@ func TestExpandError(t *testing.T) { archive := makeTestChartArchive(t, tt.chartName, tt.chartDir) dest := t.TempDir() - err := Expand(dest, archive) - assert.ErrorContains(t, err, tt.wantErr) + assert.ErrorContains(t, Expand(dest, archive), tt.wantErr) }) } } @@ -146,16 +140,12 @@ func TestExpandError(t *testing.T) { func TestExpandFile(t *testing.T) { dest := t.TempDir() - if err := ExpandFile(dest, "testdata/frobnitz-1.2.3.tgz"); err != nil { - t.Fatal(err) - } + require.NoError(t, ExpandFile(dest, "testdata/frobnitz-1.2.3.tgz")) expectedChartPath := filepath.Join(dest, "frobnitz") fi, err := os.Stat(expectedChartPath) require.NoError(t, err) - if !fi.IsDir() { - t.Fatalf("expected a chart directory at %s", expectedChartPath) - } + require.Truef(t, fi.IsDir(), "expected a chart directory at %s", expectedChartPath) dir, err := os.Open(expectedChartPath) require.NoError(t, err) diff --git a/internal/third_party/dep/fs/fs_test.go b/internal/third_party/dep/fs/fs_test.go index 797890f9f..d777b6af4 100644 --- a/internal/third_party/dep/fs/fs_test.go +++ b/internal/third_party/dep/fs/fs_test.go @@ -32,54 +32,41 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package fs import ( - "errors" "os" "path/filepath" "runtime" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestRenameWithFallback(t *testing.T) { dir := t.TempDir() - if err := RenameWithFallback(filepath.Join(dir, "does_not_exists"), filepath.Join(dir, "dst")); err == nil { - t.Fatal("expected an error for non existing file, but got nil") - } + require.Error(t, RenameWithFallback(filepath.Join(dir, "does_not_exists"), filepath.Join(dir, "dst")), "expected an error for non existing file, but got nil") srcpath := filepath.Join(dir, "src") - if srcf, err := os.Create(srcpath); err != nil { - t.Fatal(err) - } else { - srcf.Close() - } + srcf, err := os.Create(srcpath) + require.NoError(t, err) + srcf.Close() - if err := RenameWithFallback(srcpath, filepath.Join(dir, "dst")); err != nil { - t.Fatal(err) - } + require.NoError(t, RenameWithFallback(srcpath, filepath.Join(dir, "dst"))) srcpath = filepath.Join(dir, "a") - if err := os.MkdirAll(srcpath, 0o777); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(srcpath, 0o777)) dstpath := filepath.Join(dir, "b") - if err := os.MkdirAll(dstpath, 0o777); err != nil { - t.Fatal(err) - } - - if err := RenameWithFallback(srcpath, dstpath); err == nil { - t.Fatal("expected an error if dst is an existing directory, but got nil") - } + require.NoError(t, os.MkdirAll(dstpath, 0o777)) + require.Error(t, RenameWithFallback(srcpath, dstpath), "expected an error if dst is an existing directory, but got nil") } func TestCopyDir(t *testing.T) { dir := t.TempDir() srcdir := filepath.Join(dir, "src") - if err := os.MkdirAll(srcdir, 0o755); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(srcdir, 0o755)) files := []struct { path string @@ -94,61 +81,40 @@ func TestCopyDir(t *testing.T) { for i, file := range files { fn := filepath.Join(srcdir, file.path) dn := filepath.Dir(fn) - if err := os.MkdirAll(dn, 0o755); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(dn, 0o755)) fh, err := os.Create(fn) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if _, err = fh.WriteString(file.contents); err != nil { - t.Fatal(err) - } + _, err = fh.WriteString(file.contents) + require.NoError(t, err) fh.Close() files[i].fi, err = os.Stat(fn) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } destdir := filepath.Join(dir, "dest") - if err := CopyDir(srcdir, destdir); err != nil { - t.Fatal(err) - } + require.NoError(t, CopyDir(srcdir, destdir)) // Compare copy against structure indicated in 'files' for _, file := range files { fn := filepath.Join(srcdir, file.path) dn := filepath.Dir(fn) dirOK, err := IsDir(dn) - if err != nil { - t.Fatal(err) - } - if !dirOK { - t.Fatalf("expected %s to be a directory", dn) - } + require.NoError(t, err) + require.Truef(t, dirOK, "expected %s to be a directory", dn) got, err := os.ReadFile(fn) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if file.contents != string(got) { - t.Fatalf("expected: %s, got: %s", file.contents, string(got)) - } + require.Equalf(t, file.contents, string(got), "expected: %s, got: %s", file.contents, string(got)) gotinfo, err := os.Stat(fn) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if file.fi.Mode() != gotinfo.Mode() { - t.Fatalf("expected %s: %#v\n to be the same mode as %s: %#v", - file.path, file.fi.Mode(), fn, gotinfo.Mode()) - } + require.Equalf(t, file.fi.Mode(), gotinfo.Mode(), "expected %s: %#v\n to be the same mode as %s: %#v", + file.path, file.fi.Mode(), fn, gotinfo.Mode()) } } @@ -178,9 +144,7 @@ func TestCopyDirFail_SrcInaccessible(t *testing.T) { dir := t.TempDir() dstdir = filepath.Join(dir, "dst") - if err := CopyDir(srcdir, dstdir); err == nil { - t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir) - } + assert.Errorf(t, CopyDir(srcdir, dstdir), "expected error for CopyDir(%s, %s), got none", srcdir, dstdir) } func TestCopyDirFail_DstInaccessible(t *testing.T) { @@ -203,9 +167,7 @@ func TestCopyDirFail_DstInaccessible(t *testing.T) { dir := t.TempDir() srcdir = filepath.Join(dir, "src") - if err := os.MkdirAll(srcdir, 0o755); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(srcdir, 0o755)) cleanup := setupInaccessibleDir(t, func(dir string) error { dstdir = filepath.Join(dir, "dst") @@ -213,9 +175,7 @@ func TestCopyDirFail_DstInaccessible(t *testing.T) { }) defer cleanup() - if err := CopyDir(srcdir, dstdir); err == nil { - t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir) - } + assert.Errorf(t, CopyDir(srcdir, dstdir), "expected error for CopyDir(%s, %s), got none", srcdir, dstdir) } func TestCopyDirFail_SrcIsNotDir(t *testing.T) { @@ -225,44 +185,25 @@ func TestCopyDirFail_SrcIsNotDir(t *testing.T) { dir := t.TempDir() srcdir = filepath.Join(dir, "src") - if _, err = os.Create(srcdir); err != nil { - t.Fatal(err) - } + _, err = os.Create(srcdir) + require.NoError(t, err) dstdir = filepath.Join(dir, "dst") - if err = CopyDir(srcdir, dstdir); err == nil { - t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir) - } - - if !errors.Is(err, errSrcNotDir) { - t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errSrcNotDir, srcdir, dstdir, err) - } + require.ErrorIsf(t, CopyDir(srcdir, dstdir), errSrcNotDir, "expected %v error for CopyDir(%s, %s)", errSrcNotDir, srcdir, dstdir) } func TestCopyDirFail_DstExists(t *testing.T) { var srcdir, dstdir string - var err error dir := t.TempDir() srcdir = filepath.Join(dir, "src") - if err = os.MkdirAll(srcdir, 0o755); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(srcdir, 0o755)) dstdir = filepath.Join(dir, "dst") - if err = os.MkdirAll(dstdir, 0o755); err != nil { - t.Fatal(err) - } - - if err = CopyDir(srcdir, dstdir); err == nil { - t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir) - } - - if !errors.Is(err, errDstExist) { - t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errDstExist, srcdir, dstdir, err) - } + require.NoError(t, os.MkdirAll(dstdir, 0o755)) + require.ErrorIs(t, CopyDir(srcdir, dstdir), errDstExist, "expected %v error for CopyDir(%s, %s)", errDstExist, srcdir, dstdir) } func TestCopyDirFailOpen(t *testing.T) { @@ -288,70 +229,46 @@ func TestCopyDirFailOpen(t *testing.T) { dir := t.TempDir() srcdir = filepath.Join(dir, "src") - if err := os.MkdirAll(srcdir, 0o755); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(srcdir, 0o755)) srcfn := filepath.Join(srcdir, "file") srcf, err := os.Create(srcfn) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) srcf.Close() // setup source file so that it cannot be read - if err = os.Chmod(srcfn, 0o222); err != nil { - t.Fatal(err) - } + require.NoError(t, os.Chmod(srcfn, 0o222)) dstdir = filepath.Join(dir, "dst") - - if err = CopyDir(srcdir, dstdir); err == nil { - t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir) - } + assert.Errorf(t, CopyDir(srcdir, dstdir), "expected error for CopyDir(%s, %s), got none", srcdir, dstdir) } func TestCopyFile(t *testing.T) { dir := t.TempDir() srcf, err := os.Create(filepath.Join(dir, "srcfile")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) want := "hello world" - if _, err := srcf.WriteString(want); err != nil { - t.Fatal(err) - } + _, err = srcf.WriteString(want) + require.NoError(t, err) srcf.Close() destf := filepath.Join(dir, "destf") - if err := CopyFile(srcf.Name(), destf); err != nil { - t.Fatal(err) - } + require.NoError(t, CopyFile(srcf.Name(), destf)) got, err := os.ReadFile(destf) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if want != string(got) { - t.Fatalf("expected: %s, got: %s", want, string(got)) - } + require.Equalf(t, want, string(got), "expected: %s, got: %s", want, string(got)) wantinfo, err := os.Stat(srcf.Name()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) gotinfo, err := os.Stat(destf) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if wantinfo.Mode() != gotinfo.Mode() { - t.Fatalf("expected %s: %#v\n to be the same mode as %s: %#v", srcf.Name(), wantinfo.Mode(), destf, gotinfo.Mode()) - } + assert.Equalf(t, wantinfo.Mode(), gotinfo.Mode(), "expected %s: %#v\n to be the same mode as %s: %#v", srcf.Name(), wantinfo.Mode(), destf, gotinfo.Mode()) } func TestCopyFileSymlink(t *testing.T) { @@ -366,9 +283,7 @@ func TestCopyFileSymlink(t *testing.T) { for symlink, dst := range testcases { t.Run(symlink, func(t *testing.T) { var err error - if err = CopyFile(symlink, dst); err != nil { - t.Fatalf("failed to copy symlink: %s", err) - } + require.NoErrorf(t, CopyFile(symlink, dst), "failed to copy symlink") var want, got string @@ -377,31 +292,21 @@ func TestCopyFileSymlink(t *testing.T) { // regular users aren't granted usually. So we copy the file // content as a fall back instead of creating a real symlink. srcb, err := os.ReadFile(symlink) - if err != nil { - t.Fatalf("%+v", err) - } + require.NoError(t, err) dstb, err := os.ReadFile(dst) - if err != nil { - t.Fatalf("%+v", err) - } + require.NoError(t, err) want = string(srcb) got = string(dstb) } else { want, err = os.Readlink(symlink) - if err != nil { - t.Fatalf("%+v", err) - } + require.NoError(t, err) got, err = os.Readlink(dst) - if err != nil { - t.Fatalf("could not resolve symlink: %s", err) - } + require.NoErrorf(t, err, "could not resolve symlink") } - if want != got { - t.Fatalf("resolved path is incorrect. expected %s, got %s", want, got) - } + require.Equalf(t, want, got, "resolved path is incorrect. expected %s, got %s", want, got) }) } } @@ -424,9 +329,7 @@ func TestCopyFileFail(t *testing.T) { dir := t.TempDir() srcf, err := os.Create(filepath.Join(dir, "srcfile")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) srcf.Close() var dstdir string @@ -438,9 +341,7 @@ func TestCopyFileFail(t *testing.T) { defer cleanup() fn := filepath.Join(dstdir, "file") - if err := CopyFile(srcf.Name(), fn); err == nil { - t.Fatalf("expected error for %s, got none", fn) - } + require.Errorf(t, CopyFile(srcf.Name(), fn), "expected error for %s, got none", fn) } // setupInaccessibleDir creates a temporary location with a single @@ -463,9 +364,7 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { subdir := filepath.Join(dir, "dir") cleanup := func() { - if err := os.Chmod(subdir, 0o777); err != nil { - t.Error(err) - } + assert.NoError(t, os.Chmod(subdir, 0o777)) } if err := os.Mkdir(subdir, 0o777); err != nil { @@ -498,9 +397,7 @@ func TestIsDir(t *testing.T) { } wd, err := os.Getwd() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) var dn string @@ -530,14 +427,16 @@ func TestIsDir(t *testing.T) { } for f, want := range tests { - got, err := IsDir(f) - if err != nil && !want.err { - t.Fatalf("expected no error, got %v", err) - } + t.Run(f, func(t *testing.T) { + got, err := IsDir(f) - if got != want.exists { - t.Fatalf("expected %t for %s, got %t", want.exists, f, got) - } + if want.err { + require.Error(t, err) + } else { + require.NoError(t, err) + } + require.Equalf(t, want.exists, got, "expected %t for %s, got %t", want.exists, f, got) + }) } } @@ -552,26 +451,18 @@ func TestIsSymlink(t *testing.T) { dir := t.TempDir() dirPath := filepath.Join(dir, "directory") - if err := os.MkdirAll(dirPath, 0o777); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(dirPath, 0o777)) filePath := filepath.Join(dir, "file") f, err := os.Create(filePath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) f.Close() dirSymlink := filepath.Join(dir, "dirSymlink") fileSymlink := filepath.Join(dir, "fileSymlink") - if err = os.Symlink(dirPath, dirSymlink); err != nil { - t.Fatal(err) - } - if err = os.Symlink(filePath, fileSymlink); err != nil { - t.Fatal(err) - } + require.NoError(t, os.Symlink(dirPath, dirSymlink)) + require.NoError(t, os.Symlink(filePath, fileSymlink)) var ( inaccessibleFile string @@ -609,14 +500,11 @@ func TestIsSymlink(t *testing.T) { for path, want := range tests { got, err := IsSymlink(path) - if err != nil { - if !want.err { - t.Errorf("expected no error, got %v", err) - } - } - - if got != want.expected { - t.Errorf("expected %t for %s, got %t", want.expected, path, got) + if want.err { + require.Error(t, err, "expected an error") + } else { + require.NoError(t, err, "expected no error") } + assert.Equalf(t, want.expected, got, "expected %t for %s, got %t", want.expected, path, got) } } From 6c45810119937e8fcbbc4f0b7f56c32a40c52d87 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 22:30:19 +0200 Subject: [PATCH 455/571] chore(internal): refactor: convert tests to testify assert/require part 4 (#32408) refactor: convert tests to testify assert/require in internal/chart/v3/util Signed-off-by: Matthieu MOREL --- internal/chart/v3/loader/load_test.go | 281 ++++++--------------- internal/chart/v3/util/create_test.go | 41 ++- internal/chart/v3/util/save_test.go | 32 +-- internal/plugin/runtime_subprocess_test.go | 4 +- internal/release/v2/info_test.go | 42 ++- 5 files changed, 121 insertions(+), 279 deletions(-) diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index 5caec7815..9378bcdf5 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -41,13 +41,9 @@ import ( func TestLoadDir(t *testing.T) { l, err := Loader("testdata/frobnitz") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyFrobnitz(t, c) verifyChart(t, c) verifyDependencies(t, c) @@ -60,33 +56,24 @@ func TestLoadDirWithDevNull(t *testing.T) { } l, err := Loader("testdata/frobnitz_with_dev_null") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - if _, err := l.Load(); err == nil { - t.Error("packages with an irregular file (/dev/null) should not load") - } + require.NoError(t, err, "Failed to load testdata") + _, err = l.Load() + assert.Error(t, err, "packages with an irregular file (/dev/null) should not load") } func TestLoadDirWithSymlink(t *testing.T) { sym := filepath.Join("..", "LICENSE") link := filepath.Join("testdata", "frobnitz_with_symlink", "LICENSE") - if err := os.Symlink(sym, link); err != nil { - t.Fatal(err) - } + require.NoError(t, os.Symlink(sym, link)) defer os.Remove(link) l, err := Loader("testdata/frobnitz_with_symlink") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyFrobnitz(t, c) verifyChart(t, c) verifyDependencies(t, c) @@ -109,8 +96,7 @@ func TestBomTestData(t *testing.T) { defer unzipped.Close() for _, testFile := range testFiles { data := make([]byte, 3) - err := unzipped.Reset(bytes.NewReader(archive)) - require.NoErrorf(t, err, "Error reading archive frobnitz_with_bom.tgz") + require.NoErrorf(t, unzipped.Reset(bytes.NewReader(archive)), "Error reading archive frobnitz_with_bom.tgz") tr := tar.NewReader(unzipped) for { file, err := tr.Next() @@ -132,13 +118,9 @@ func TestBomTestData(t *testing.T) { func TestLoadDirWithUTFBOM(t *testing.T) { l, err := Loader("testdata/frobnitz_with_bom") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyFrobnitz(t, c) verifyChart(t, c) verifyDependencies(t, c) @@ -148,13 +130,9 @@ func TestLoadDirWithUTFBOM(t *testing.T) { func TestLoadArchiveWithUTFBOM(t *testing.T) { l, err := Loader("testdata/frobnitz_with_bom.tgz") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyFrobnitz(t, c) verifyChart(t, c) verifyDependencies(t, c) @@ -164,13 +142,9 @@ func TestLoadArchiveWithUTFBOM(t *testing.T) { func TestLoadFile(t *testing.T) { l, err := Loader("testdata/frobnitz-1.2.3.tgz") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyFrobnitz(t, c) verifyChart(t, c) verifyDependencies(t, c) @@ -224,36 +198,18 @@ icon: https://example.com/64x64.png } c, err := LoadFiles(goodFiles) - if err != nil { - t.Errorf("Expected good files to be loaded, got %v", err) - } - - if c.Name() != "frobnitz" { - t.Errorf("Expected chart name to be 'frobnitz', got %s", c.Name()) - } - - if c.Values["var"] != "some values" { - t.Error("Expected chart values to be populated with default values") - } + require.NoError(t, err, "Expected good files to be loaded") + assert.Equal(t, "frobnitz", c.Name(), "Expected chart name to be 'frobnitz', got %s", c.Name()) + assert.Equal(t, "some values", c.Values["var"], "Expected chart values to be populated with default values") + assert.Len(t, c.Raw, 5, "Expected %d files, got %d", 5, len(c.Raw)) - if len(c.Raw) != 5 { - t.Errorf("Expected %d files, got %d", 5, len(c.Raw)) - } + assert.True(t, bytes.Equal(c.Schema, []byte("type: Values")), "Expected chart schema to be populated with default values") - if !bytes.Equal(c.Schema, []byte("type: Values")) { - t.Error("Expected chart schema to be populated with default values") - } + assert.Len(t, c.Templates, 2, "Expected number of templates == 2, got %d", len(c.Templates)) - if len(c.Templates) != 2 { - t.Errorf("Expected number of templates == 2, got %d", len(c.Templates)) - } - - if _, err = LoadFiles([]*archive.BufferedFile{}); err == nil { - t.Fatal("Expected err to be non-nil") - } - if err.Error() != "Chart.yaml file is missing" { - t.Errorf("Expected chart metadata missing error, got '%s'", err.Error()) - } + _, err = LoadFiles([]*archive.BufferedFile{}) + require.Error(t, err, "Expected err to be non-nil") + assert.EqualError(t, err, "Chart.yaml file is missing", "Expected chart metadata missing error, got '%s'", err.Error()) } // Test the order of file loading. The Chart.yaml file needs to come first for @@ -309,9 +265,7 @@ icon: https://example.com/64x64.png // Capture stderr to make sure message about Chart.yaml handle dependencies // is not present r, w, err := os.Pipe() - if err != nil { - t.Fatalf("Unable to create pipe: %s", err) - } + require.NoError(t, err, "Unable to create pipe") stderr := log.Writer() log.SetOutput(w) defer func() { @@ -319,25 +273,19 @@ icon: https://example.com/64x64.png }() _, err = LoadFiles(goodFiles) - if err != nil { - t.Errorf("Expected good files to be loaded, got %v", err) - } + require.NoError(t, err, "Expected good files to be loaded") w.Close() var text bytes.Buffer io.Copy(&text, r) - if text.String() != "" { - t.Errorf("Expected no message to Stderr, got %s", text.String()) - } + assert.Empty(t, text.String(), "Expected no message to Stderr, got %s", text.String()) } // Packaging the chart on a Windows machine will produce an // archive that has \\ as delimiters. Test that we support these archives func TestLoadFileBackslash(t *testing.T) { c, err := Load("testdata/frobnitz_backslash-1.2.3.tgz") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyChartFileAndTemplate(t, c, "frobnitz_backslash") verifyChart(t, c) verifyDependencies(t, c) @@ -345,13 +293,9 @@ func TestLoadFileBackslash(t *testing.T) { func TestLoadV3WithReqs(t *testing.T) { l, err := Loader("testdata/frobnitz.v3.reqs") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyDependencies(t, c) verifyDependenciesLock(t, c) } @@ -361,9 +305,7 @@ func TestLoadInvalidArchive(t *testing.T) { writeTar := func(filename, internalPath string, body []byte) { dest, err := os.Create(filename) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) zipper := gzip.NewWriter(dest) tw := tar.NewWriter(zipper) @@ -373,12 +315,9 @@ func TestLoadInvalidArchive(t *testing.T) { Size: int64(len(body)), ModTime: time.Now(), } - if err := tw.WriteHeader(h); err != nil { - t.Fatal(err) - } - if _, err := tw.Write(body); err != nil { - t.Fatal(err) - } + require.NoError(t, tw.WriteHeader(h)) + _, err = tw.Write(body) + require.NoError(t, err) tw.Close() zipper.Close() dest.Close() @@ -410,37 +349,27 @@ func TestLoadInvalidArchive(t *testing.T) { illegalChart := filepath.Join(tmpdir, tt.chartname) writeTar(illegalChart, tt.internal, []byte("hello: world")) _, err := Load(illegalChart) - if err == nil { - t.Fatal("expected error when unpacking illegal files") - } - if !strings.Contains(err.Error(), tt.expectError) { - t.Errorf("Expected error to contain %q, got %q for %s", tt.expectError, err.Error(), tt.chartname) - } + require.Error(t, err, "expected error when unpacking illegal files") + require.ErrorContains(t, err, tt.expectError, "Expected error to contain %q, got %q for %s", tt.expectError, err.Error(), tt.chartname) } // Make sure that absolute path gets interpreted as relative illegalChart := filepath.Join(tmpdir, "abs-path.tgz") writeTar(illegalChart, "/Chart.yaml", []byte("hello: world")) _, err := Load(illegalChart) - if err.Error() != "validation: chart.metadata.name is required" { - t.Error(err) - } + require.EqualError(t, err, "validation: chart.metadata.name is required") // And just to validate that the above was not spurious illegalChart = filepath.Join(tmpdir, "abs-path2.tgz") writeTar(illegalChart, "files/whatever.yaml", []byte("hello: world")) _, err = Load(illegalChart) - if err.Error() != "Chart.yaml file is missing" { - t.Errorf("Unexpected error message: %s", err) - } + require.EqualError(t, err, "Chart.yaml file is missing") // Finally, test that drive letter gets stripped off on Windows illegalChart = filepath.Join(tmpdir, "abs-winpath.tgz") writeTar(illegalChart, "c:\\Chart.yaml", []byte("hello: world")) _, err = Load(illegalChart) - if err.Error() != "validation: chart.metadata.name is required" { - t.Error(err) - } + assert.EqualError(t, err, "validation: chart.metadata.name is required") } func TestLoadValues(t *testing.T) { @@ -487,12 +416,8 @@ foo: for testName, testCase := range testCases { t.Run(testName, func(tt *testing.T) { values, err := LoadValues(bytes.NewReader(testCase.data)) - if err != nil { - tt.Fatal(err) - } - if !reflect.DeepEqual(values, testCase.expctedValues) { - tt.Errorf("Expected values: %v, got %v", testCase.expctedValues, values) - } + require.NoError(tt, err) + assert.Truef(tt, reflect.DeepEqual(values, testCase.expctedValues), "Expected values: %v, got %v", testCase.expctedValues, values) }) } } @@ -521,21 +446,15 @@ func TestMergeValuesV3(t *testing.T) { testMap := MergeMaps(flatMap, nestedMap) equal := reflect.DeepEqual(testMap, nestedMap) - if !equal { - t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) - } + assert.True(t, equal, "Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) testMap = MergeMaps(nestedMap, flatMap) equal = reflect.DeepEqual(testMap, flatMap) - if !equal { - t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) - } + assert.True(t, equal, "Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) testMap = MergeMaps(nestedMap, anotherNestedMap) equal = reflect.DeepEqual(testMap, anotherNestedMap) - if !equal { - t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) - } + assert.True(t, equal, "Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) testMap = MergeMaps(anotherFlatMap, anotherNestedMap) expectedMap := map[string]any{ @@ -547,20 +466,14 @@ func TestMergeValuesV3(t *testing.T) { }, } equal = reflect.DeepEqual(testMap, expectedMap) - if !equal { - t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) - } + assert.True(t, equal, "Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) } func verifyChart(t *testing.T, c *chart.Chart) { t.Helper() - if c.Name() == "" { - t.Fatalf("No chart metadata found on %v", c) - } + require.NotEmpty(t, c.Name(), "No chart metadata found on %v", c) t.Logf("Verifying chart %s", c.Name()) - if len(c.Templates) != 1 { - t.Errorf("Expected 1 template, got %d", len(c.Templates)) - } + assert.Len(t, c.Templates, 1, "Expected 1 template, got %d", len(c.Templates)) numfiles := 6 if len(c.Files) != numfiles { @@ -587,62 +500,40 @@ func verifyChart(t *testing.T, c *chart.Chart) { } for _, dep := range c.Dependencies() { - if dep.Metadata == nil { - t.Fatalf("expected metadata on dependency: %v", dep) - } + require.NotNil(t, dep.Metadata, "expected metadata on dependency: %v", dep) exp, ok := expect[dep.Name()] - if !ok { - t.Fatalf("Unknown dependency %s", dep.Name()) - } - if exp["version"] != dep.Metadata.Version { - t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version) - } + require.True(t, ok, "Unknown dependency %s", dep.Name()) + assert.Equal(t, exp["version"], dep.Metadata.Version, "Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version) } } func verifyDependencies(t *testing.T, c *chart.Chart) { t.Helper() - if len(c.Metadata.Dependencies) != 2 { - t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) - } + assert.Len(t, c.Metadata.Dependencies, 2, "Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) tests := []*chart.Dependency{ {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, } for i, tt := range tests { d := c.Metadata.Dependencies[i] - if d.Name != tt.Name { - t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name) - } - if d.Version != tt.Version { - t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version) - } - if d.Repository != tt.Repository { - t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository) - } + assert.Equal(t, tt.Name, d.Name, "Expected dependency named %q, got %q", tt.Name, d.Name) + assert.Equal(t, tt.Version, d.Version, "Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version) + assert.Equal(t, tt.Repository, d.Repository, "Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository) } } func verifyDependenciesLock(t *testing.T, c *chart.Chart) { t.Helper() - if len(c.Metadata.Dependencies) != 2 { - t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) - } + assert.Len(t, c.Metadata.Dependencies, 2, "Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) tests := []*chart.Dependency{ {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, } for i, tt := range tests { d := c.Metadata.Dependencies[i] - if d.Name != tt.Name { - t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name) - } - if d.Version != tt.Version { - t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version) - } - if d.Repository != tt.Repository { - t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository) - } + assert.Equal(t, tt.Name, d.Name, "Expected dependency named %q, got %q", tt.Name, d.Name) + assert.Equal(t, tt.Version, d.Version, "Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version) + assert.Equal(t, tt.Repository, d.Repository, "Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository) } } @@ -653,51 +544,25 @@ func verifyFrobnitz(t *testing.T, c *chart.Chart) { func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) { t.Helper() - if c.Metadata == nil { - t.Fatal("Metadata is nil") - } - if c.Name() != name { - t.Errorf("Expected %s, got %s", name, c.Name()) - } - if len(c.Templates) != 1 { - t.Fatalf("Expected 1 template, got %d", len(c.Templates)) - } - if c.Templates[0].Name != "templates/template.tpl" { - t.Errorf("Unexpected template: %s", c.Templates[0].Name) - } - if len(c.Templates[0].Data) == 0 { - t.Error("No template data.") - } - if len(c.Files) != 6 { - t.Fatalf("Expected 6 Files, got %d", len(c.Files)) - } - if len(c.Dependencies()) != 2 { - t.Fatalf("Expected 2 Dependency, got %d", len(c.Dependencies())) - } - if len(c.Metadata.Dependencies) != 2 { - t.Fatalf("Expected 2 Dependencies.Dependency, got %d", len(c.Metadata.Dependencies)) - } - if len(c.Lock.Dependencies) != 2 { - t.Fatalf("Expected 2 Lock.Dependency, got %d", len(c.Lock.Dependencies)) - } + require.NotNil(t, c.Metadata, "Metadata is nil") + assert.Equal(t, name, c.Name(), "Expected %s, got %s", name, c.Name()) + require.Len(t, c.Templates, 1, "Expected 1 template, got %d", len(c.Templates)) + assert.Equal(t, "templates/template.tpl", c.Templates[0].Name, "Unexpected template: %s", c.Templates[0].Name) + assert.NotEmpty(t, c.Templates[0].Data, "No template data.") + require.Len(t, c.Files, 6, "Expected 6 Files, got %d", len(c.Files)) + require.Len(t, c.Dependencies(), 2, "Expected 2 Dependency, got %d", len(c.Dependencies())) + require.Len(t, c.Metadata.Dependencies, 2, "Expected 2 Dependencies.Dependency, got %d", len(c.Metadata.Dependencies)) + require.Len(t, c.Lock.Dependencies, 2, "Expected 2 Lock.Dependency, got %d", len(c.Lock.Dependencies)) for _, dep := range c.Dependencies() { switch dep.Name() { case "mariner": case "alpine": - if len(dep.Templates) != 1 { - t.Fatalf("Expected 1 template, got %d", len(dep.Templates)) - } - if dep.Templates[0].Name != "templates/alpine-pod.yaml" { - t.Errorf("Unexpected template: %s", dep.Templates[0].Name) - } + require.Len(t, dep.Templates, 1, "Expected 1 template, got %d", len(dep.Templates)) + assert.Equal(t, "templates/alpine-pod.yaml", dep.Templates[0].Name, "Unexpected template: %s", dep.Templates[0].Name) assert.NotEmpty(t, dep.Templates[0].Data, "No template data.") - if len(dep.Files) != 1 { - t.Fatalf("Expected 1 Files, got %d", len(dep.Files)) - } - if len(dep.Dependencies()) != 2 { - t.Fatalf("Expected 2 Dependency, got %d", len(dep.Dependencies())) - } + require.Len(t, dep.Files, 1, "Expected 1 Files, got %d", len(dep.Files)) + require.Len(t, dep.Dependencies(), 2, "Expected 2 Dependency, got %d", len(dep.Dependencies())) default: t.Errorf("Unexpected dependency %s", dep.Name()) } @@ -707,8 +572,6 @@ func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) { func verifyBomStripped(t *testing.T, files []*common.File) { t.Helper() for _, file := range files { - if bytes.HasPrefix(file.Data, utf8bom) { - t.Errorf("Byte Order Mark still present in processed file %s", file.Name) - } + assert.Falsef(t, bytes.HasPrefix(file.Data, utf8bom), "Byte Order Mark still present in processed file %s", file.Name) } } diff --git a/internal/chart/v3/util/create_test.go b/internal/chart/v3/util/create_test.go index eba0811ae..ddbbf6216 100644 --- a/internal/chart/v3/util/create_test.go +++ b/internal/chart/v3/util/create_test.go @@ -38,7 +38,7 @@ func TestCreate(t *testing.T) { dir := filepath.Join(tdir, "foo") mychart, err := loader.LoadDir(c) - require.NoError(t, err, "Failed to load newly created chart %q: %s", c, err) + require.NoError(t, err, "Failed to load newly created chart %q", c) assert.Equal(t, "foo", mychart.Name(), "Expected name to be 'foo', got %q", mychart.Name()) @@ -55,9 +55,8 @@ func TestCreate(t *testing.T) { TestConnectionName, ValuesfileName, } { - if _, err := os.Stat(filepath.Join(dir, f)); err != nil { - t.Errorf("Expected %s file: %s", f, err) - } + _, err := os.Stat(filepath.Join(dir, f)) + assert.NoErrorf(t, err, "Expected %s file", f) } } @@ -71,14 +70,12 @@ func TestCreateFrom(t *testing.T) { } srcdir := "./testdata/frobnitz/charts/mariner" - if err := CreateFrom(cf, tdir, srcdir); err != nil { - t.Fatal(err) - } + require.NoError(t, CreateFrom(cf, tdir, srcdir)) dir := filepath.Join(tdir, "foo") c := filepath.Join(tdir, cf.Name) mychart, err := loader.LoadDir(c) - require.NoError(t, err, "Failed to load newly created chart %q: %s", c, err) + require.NoError(t, err, "Failed to load newly created chart %q", c) assert.Equal(t, "foo", mychart.Name(), "Expected name to be 'foo', got %q", mychart.Name()) @@ -87,16 +84,13 @@ func TestCreateFrom(t *testing.T) { ValuesfileName, filepath.Join(TemplatesDir, "placeholder.tpl"), } { - if _, err := os.Stat(filepath.Join(dir, f)); err != nil { - t.Errorf("Expected %s file: %s", f, err) - } + _, err := os.Stat(filepath.Join(dir, f)) + require.NoErrorf(t, err, "Expected %s file", f) // Check each file to make sure has been replaced b, err := os.ReadFile(filepath.Join(dir, f)) - require.NoError(t, err, "Unable to read file %s: %s", f, err) - if bytes.Contains(b, []byte("")) { - t.Errorf("File %s contains ", f) - } + require.NoError(t, err, "Unable to read file %s", f) + assert.Falsef(t, bytes.Contains(b, []byte("")), "File %s contains ", f) } } @@ -106,9 +100,8 @@ func TestCreate_Overwrite(t *testing.T) { var errlog bytes.Buffer - if _, err := Create("foo", tdir); err != nil { - t.Fatal(err) - } + _, err := Create("foo", tdir) + require.NoError(t, err) dir := filepath.Join(tdir, "foo") @@ -117,9 +110,8 @@ func TestCreate_Overwrite(t *testing.T) { // Now re-run the create Stderr = &errlog - if _, err := Create("foo", tdir); err != nil { - t.Fatal(err) - } + _, err = Create("foo", tdir) + require.NoError(t, err) data, err := os.ReadFile(tplname) require.NoError(t, err) @@ -150,8 +142,11 @@ func TestValidateChartName(t *testing.T) { "abcdefghijklmnopqrstuvwxyz-_." + "ABCDEFGHIJKLMNOPQRSTUVWXYZ-_.": false, } { - if err := validateChartName(name); (err != nil) == shouldPass { - t.Errorf("test for %q failed", name) + err := validateChartName(name) + if shouldPass { + assert.NoErrorf(t, err, "Expected chart name %q to pass validation", name) + } else { + assert.Errorf(t, err, "Expected chart name %q to fail validation, but it passed", name) } } } diff --git a/internal/chart/v3/util/save_test.go b/internal/chart/v3/util/save_test.go index e729ad3b3..f7bd5dcfb 100644 --- a/internal/chart/v3/util/save_test.go +++ b/internal/chart/v3/util/save_test.go @@ -62,13 +62,9 @@ func TestSave(t *testing.T) { chartWithInvalidJSON := withSchema(*c, []byte("{")) where, err := Save(c, dest) - require.NoError(t, err, "Failed to save: %s", err) - if !strings.HasPrefix(where, dest) { - t.Fatalf("Expected %q to start with %q", where, dest) - } - if !strings.HasSuffix(where, ".tgz") { - t.Fatalf("Expected %q to end with .tgz", where) - } + require.NoError(t, err, "Failed to save") + require.Truef(t, strings.HasPrefix(where, dest), "Expected %q to start with %q", where, dest) + require.Truef(t, strings.HasSuffix(where, ".tgz"), "Expected %q to end with .tgz", where) c2, err := loader.LoadFile(where) require.NoError(t, err) @@ -83,13 +79,12 @@ func TestSave(t *testing.T) { formattedActual := Indent(indentation, string(c2.Schema)) t.Fatalf("Schema data did not match.\nExpected:\n%s\nActual:\n%s", formattedExpected, formattedActual) } - if _, err := Save(&chartWithInvalidJSON, dest); err == nil { - t.Fatal("Invalid JSON was not caught while saving chart") - } + _, err = Save(&chartWithInvalidJSON, dest) + require.Error(t, err, "Invalid JSON was not caught while saving chart") c.Metadata.APIVersion = chart.APIVersionV3 where, err = Save(c, dest) - require.NoError(t, err, "Failed to save: %s", err) + require.NoError(t, err, "Failed to save") c2, err = loader.LoadFile(where) require.NoError(t, err) require.NotNil(t, c2.Lock, "Expected v3 chart archive to contain a Chart.lock file") @@ -152,16 +147,14 @@ func TestSavePreservesTimestamps(t *testing.T) { } where, err := Save(c, tmp) - require.NoError(t, err, "Failed to save: %s", err) + require.NoError(t, err, "Failed to save") allHeaders, err := retrieveAllHeadersFromTar(where) - require.NoError(t, err, "Failed to parse tar: %v", err) + require.NoError(t, err, "Failed to parse tar") roundedTime := initialCreateTime.Round(time.Second) for _, header := range allHeaders { - if !header.ModTime.Equal(roundedTime) { - t.Fatalf("File timestamp not preserved: %v", header.ModTime) - } + require.Truef(t, header.ModTime.Equal(roundedTime), "File timestamp not preserved: %v", header.ModTime) } } @@ -235,8 +228,7 @@ func TestSaveDir(t *testing.T) { pth := filepath.Join(tmp2, "tmpcharts") require.NoError(t, os.MkdirAll(filepath.Join(pth), 0o755), "Failed to create directory") - err = SaveDir(c, pth) - assert.EqualError(t, err, "\"../ahab\" is not a valid chart name", "Did not get expected error for chart named %q", c.Name()) + assert.EqualError(t, SaveDir(c, pth), "\"../ahab\" is not a valid chart name", "Did not get expected error for chart named %q", c.Name()) } func TestRepeatableSave(t *testing.T) { @@ -297,10 +289,10 @@ func TestRepeatableSave(t *testing.T) { // create package dest := path.Join(tmp, "newdir") where, err := Save(test.chart, dest) - require.NoError(t, err, "Failed to save: %s", err) + require.NoError(t, err, "Failed to save") // get shasum for package result, err := sha256Sum(where) - require.NoError(t, err, "Failed to check shasum: %s", err) + require.NoError(t, err, "Failed to check shasum") // assert that the package SHA is what we wanted. assert.Equal(t, test.want, result, "FormatName() result = %v, want %v", result, test.want) }) diff --git a/internal/plugin/runtime_subprocess_test.go b/internal/plugin/runtime_subprocess_test.go index c651dd3f7..c04127e0d 100644 --- a/internal/plugin/runtime_subprocess_test.go +++ b/internal/plugin/runtime_subprocess_test.go @@ -16,7 +16,6 @@ limitations under the License. package plugin import ( - "errors" "fmt" "os" "path/filepath" @@ -78,8 +77,7 @@ func TestSubprocessPluginRuntime(t *testing.T) { require.Error(t, err) ieerr := &InvokeExecError{} - ok := errors.As(err, &ieerr) - require.True(t, ok, "expected InvokeExecError, got %T", err) + require.ErrorAs(t, err, &ieerr, "expected InvokeExecError") assert.Equal(t, 56, ieerr.ExitCode) assert.Nil(t, output) diff --git a/internal/release/v2/info_test.go b/internal/release/v2/info_test.go index 5812c6560..571daa025 100644 --- a/internal/release/v2/info_test.go +++ b/internal/release/v2/info_test.go @@ -253,17 +253,17 @@ func TestInfoUnmarshalJSON(t *testing.T) { err := json.Unmarshal([]byte(tt.input), &info) if tt.wantErr { assert.Error(t, err) - return + } else { + require.NoError(t, err) + assert.Equal(t, tt.expected.FirstDeployed.Unix(), info.FirstDeployed.Unix()) + assert.Equal(t, tt.expected.LastDeployed.Unix(), info.LastDeployed.Unix()) + assert.Equal(t, tt.expected.Deleted.Unix(), info.Deleted.Unix()) + assert.Equal(t, tt.expected.Description, info.Description) + assert.Equal(t, tt.expected.Status, info.Status) + assert.Equal(t, tt.expected.RollbackRevision, info.RollbackRevision) + assert.Equal(t, tt.expected.Notes, info.Notes) + assert.Equal(t, tt.expected.Resources, info.Resources) } - require.NoError(t, err) - assert.Equal(t, tt.expected.FirstDeployed.Unix(), info.FirstDeployed.Unix()) - assert.Equal(t, tt.expected.LastDeployed.Unix(), info.LastDeployed.Unix()) - assert.Equal(t, tt.expected.Deleted.Unix(), info.Deleted.Unix()) - assert.Equal(t, tt.expected.Description, info.Description) - assert.Equal(t, tt.expected.Status, info.Status) - assert.Equal(t, tt.expected.RollbackRevision, info.RollbackRevision) - assert.Equal(t, tt.expected.Notes, info.Notes) - assert.Equal(t, tt.expected.Resources, info.Resources) }) } } @@ -284,9 +284,7 @@ func TestInfoRoundTrip(t *testing.T) { require.NoError(t, err) var decoded Info - err = json.Unmarshal(data, &decoded) - require.NoError(t, err) - + require.NoError(t, json.Unmarshal(data, &decoded)) assert.Equal(t, original.FirstDeployed.Unix(), decoded.FirstDeployed.Unix()) assert.Equal(t, original.LastDeployed.Unix(), decoded.LastDeployed.Unix()) assert.Equal(t, original.Deleted.Unix(), decoded.Deleted.Unix()) @@ -330,8 +328,7 @@ func TestInfoRollbackRevisionRoundTrip(t *testing.T) { require.NoError(t, err) var decoded Info - err = json.Unmarshal(data, &decoded) - require.NoError(t, err) + require.NoError(t, json.Unmarshal(data, &decoded)) assert.Equal(t, tt.info.RollbackRevision, decoded.RollbackRevision) assert.Equal(t, tt.info.FirstDeployed.Unix(), decoded.FirstDeployed.Unix()) @@ -342,8 +339,7 @@ func TestInfoRollbackRevisionRoundTrip(t *testing.T) { // Verify omitempty behavior: zero rollback_revision should not appear in JSON if tt.info.RollbackRevision == 0 { var raw map[string]any - err = json.Unmarshal(data, &raw) - require.NoError(t, err) + require.NoError(t, json.Unmarshal(data, &raw)) assert.NotContains(t, raw, "rollback_revision") } }) @@ -356,13 +352,12 @@ func TestInfoEmptyStringRoundTrip(t *testing.T) { input := `{"first_deployed":"","last_deployed":"","deleted":"","status":"deployed","description":"test"}` var info Info - err := json.Unmarshal([]byte(input), &info) - require.NoError(t, err) + require.NoError(t, json.Unmarshal([]byte(input), &info)) // Verify time fields are zero values - assert.True(t, info.FirstDeployed.IsZero()) - assert.True(t, info.LastDeployed.IsZero()) - assert.True(t, info.Deleted.IsZero()) + assert.Zero(t, info.FirstDeployed) + assert.Zero(t, info.LastDeployed) + assert.Zero(t, info.Deleted) assert.Equal(t, common.StatusDeployed, info.Status) assert.Equal(t, "test", info.Description) @@ -371,8 +366,7 @@ func TestInfoEmptyStringRoundTrip(t *testing.T) { require.NoError(t, err) var result map[string]any - err = json.Unmarshal(data, &result) - require.NoError(t, err) + require.NoError(t, json.Unmarshal(data, &result)) // Zero time values should be omitted due to omitzero tag assert.NotContains(t, result, "first_deployed") From 8c7f3a14d2cbde663847641b1f4274d266e6f0f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:09:32 -0600 Subject: [PATCH 456/571] chore(deps): bump actions/checkout from 7.0.0 to 7.0.1 (#32411) Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0...3d3c42e5aac5ba805825da76410c181273ba90b1) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-test.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/govulncheck.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 066adf1ce..9503674ed 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # pin@v7.0.1 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b0a03b342..d02d55b82 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -44,7 +44,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # pin@v7.0.1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index c5312e936..955f12002 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # pin@v7.0.1 - name: Add variables to environment file run: cat ".github/env" >> "$GITHUB_ENV" - name: Setup Go diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 0aee4c01c..0fae0be3a 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # pin@v7.0.1 with: persist-credentials: false - name: Add variables to environment file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3bed1e12a..2b2f93f4d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest-16-cores steps: - name: Checkout source code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # pin@v7.0.1 with: fetch-depth: 0 @@ -85,7 +85,7 @@ jobs: if: github.ref == 'refs/heads/main' && github.repository == 'helm/helm' steps: - name: Checkout source code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # pin@v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # pin@v7.0.1 with: fetch-depth: 0 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 3a8410ced..41f97dd45 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -28,7 +28,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false From df2d013517b0a237ea8858677558db3129a92911 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:56 +0200 Subject: [PATCH 457/571] chore(pkg): refactor: convert tests to testify assert/require part 3 refactor: convert tests to testify assert/require in pkg Signed-off-by: Matthieu MOREL --- pkg/chart/v2/util/chartfile_test.go | 107 +++------- pkg/repo/v1/index_test.go | 309 ++++++++-------------------- 2 files changed, 119 insertions(+), 297 deletions(-) diff --git a/pkg/chart/v2/util/chartfile_test.go b/pkg/chart/v2/util/chartfile_test.go index 068977eef..bdcb506da 100644 --- a/pkg/chart/v2/util/chartfile_test.go +++ b/pkg/chart/v2/util/chartfile_test.go @@ -19,6 +19,9 @@ package util import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/pkg/chart/v2" ) @@ -26,96 +29,44 @@ const testfile = "testdata/chartfiletest.yaml" func TestLoadChartfile(t *testing.T) { f, err := LoadChartfile(testfile) - if err != nil { - t.Errorf("Failed to open %s: %s", testfile, err) - return - } + require.NoErrorf(t, err, "Failed to open %s", testfile) verifyChartfile(t, f, "frobnitz") } func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { t.Helper() - if f == nil { - t.Fatal("Failed verifyChartfile because f is nil") - } - - if f.APIVersion != chart.APIVersionV1 { - t.Errorf("Expected API Version %q, got %q", chart.APIVersionV1, f.APIVersion) - } - - if f.Name != name { - t.Errorf("Expected %s, got %s", name, f.Name) - } - - if f.Description != "This is a frobnitz." { - t.Errorf("Unexpected description %q", f.Description) - } - - if f.Version != "1.2.3" { - t.Errorf("Unexpected version %q", f.Version) - } - - if len(f.Maintainers) != 2 { - t.Errorf("Expected 2 maintainers, got %d", len(f.Maintainers)) - } - - if f.Maintainers[0].Name != "The Helm Team" { - t.Error("Unexpected maintainer name.") - } - - if f.Maintainers[1].Email != "nobody@example.com" { - t.Error("Unexpected maintainer email.") - } - - if len(f.Sources) != 1 { - t.Fatal("Unexpected number of sources") - } - - if f.Sources[0] != "https://example.com/foo/bar" { - t.Errorf("Expected https://example.com/foo/bar, got %s", f.Sources) - } - - if f.Home != "http://example.com" { - t.Error("Unexpected home.") - } - - if f.Icon != "https://example.com/64x64.png" { - t.Errorf("Unexpected icon: %q", f.Icon) - } - - if len(f.Keywords) != 3 { - t.Error("Unexpected keywords") - } - - if len(f.Annotations) != 2 { - t.Fatal("Unexpected annotations") - } - - if want, got := "extravalue", f.Annotations["extrakey"]; want != got { - t.Errorf("Want %q, but got %q", want, got) - } - - if want, got := "anothervalue", f.Annotations["anotherkey"]; want != got { - t.Errorf("Want %q, but got %q", want, got) - } + require.NotNil(t, f, "Failed verifyChartfile because f is nil") + assert.Equal(t, chart.APIVersionV1, f.APIVersion, "Expected API Version %q, got %q", chart.APIVersionV1, f.APIVersion) + assert.Equal(t, name, f.Name, "Expected %s, got %s", name, f.Name) + assert.Equal(t, "This is a frobnitz.", f.Description, "Unexpected description %q", f.Description) + assert.Equal(t, "1.2.3", f.Version, "Unexpected version %q", f.Version) + assert.Len(t, f.Maintainers, 2, "Expected 2 maintainers, got %d", len(f.Maintainers)) + assert.Equal(t, "The Helm Team", f.Maintainers[0].Name, "Unexpected maintainer name.") + assert.Equal(t, "nobody@example.com", f.Maintainers[1].Email, "Unexpected maintainer email.") + require.Len(t, f.Sources, 1, "Unexpected number of sources") + assert.Equal(t, "https://example.com/foo/bar", f.Sources[0], "Expected https://example.com/foo/bar, got %s", f.Sources) + assert.Equal(t, "http://example.com", f.Home, "Unexpected home.") + assert.Equal(t, "https://example.com/64x64.png", f.Icon, "Unexpected icon: %q", f.Icon) + assert.Len(t, f.Keywords, 3, "Unexpected keywords") + require.Len(t, f.Annotations, 2, "Unexpected annotations") + + want, got := "extravalue", f.Annotations["extrakey"] + assert.Equalf(t, want, got, "Want %q, but got %q", want, got) + + want, got = "anothervalue", f.Annotations["anotherkey"] + assert.Equalf(t, want, got, "Want %q, but got %q", want, got) kk := []string{"frobnitz", "sprocket", "dodad"} for i, k := range f.Keywords { - if kk[i] != k { - t.Errorf("Expected %q, got %q", kk[i], k) - } + assert.Equal(t, kk[i], k, "Expected %q, got %q", kk[i], k) } } func TestIsChartDir(t *testing.T) { validChartDir, err := IsChartDir("testdata/frobnitz") - if !validChartDir { - t.Errorf("unexpected error while reading chart-directory: (%v)", err) - return - } + require.NoError(t, err) + require.True(t, validChartDir, "expected valid chart directory") validChartDir, err = IsChartDir("testdata") - if validChartDir || err == nil { - t.Error("expected error but did not get any") - return - } + require.Error(t, err) + require.False(t, validChartDir, "expected invalid chart directory") } diff --git a/pkg/repo/v1/index_test.go b/pkg/repo/v1/index_test.go index 126be125f..88aed14d7 100644 --- a/pkg/repo/v1/index_test.go +++ b/pkg/repo/v1/index_test.go @@ -28,6 +28,9 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/getter" @@ -95,33 +98,22 @@ func TestIndexFile(t *testing.T) { {&chart.Metadata{APIVersion: "v2", Name: "setter", Version: "0.1.8"}, "setter-0.1.8.tgz", "http://example.com/charts", "sha256:1234567890abc"}, {&chart.Metadata{APIVersion: "v2", Name: "setter", Version: "0.1.8+beta"}, "setter-0.1.8+beta.tgz", "http://example.com/charts", "sha256:1234567890abc"}, } { - if err := i.MustAdd(x.md, x.filename, x.baseURL, x.digest); err != nil { - t.Errorf("unexpected error adding to index: %s", err) - } + require.NoErrorf(t, i.MustAdd(x.md, x.filename, x.baseURL, x.digest), "unexpected error adding to index") } i.SortEntries() - if i.APIVersion != APIVersionV1 { - t.Error("Expected API version v1") - } + assert.Equal(t, APIVersionV1, i.APIVersion, "Expected API version v1") - if len(i.Entries) != 3 { - t.Errorf("Expected 3 charts. Got %d", len(i.Entries)) - } + assert.Lenf(t, i.Entries, 3, "Expected 3 charts. Got %d", len(i.Entries)) - if i.Entries["clipper"][0].Name != "clipper" { - t.Errorf("Expected clipper, got %s", i.Entries["clipper"][0].Name) - } + assert.Equalf(t, "clipper", i.Entries["clipper"][0].Name, "Expected clipper, got %s", i.Entries["clipper"][0].Name) - if len(i.Entries["cutter"]) != 3 { - t.Error("Expected three cutters.") - } + assert.Len(t, i.Entries["cutter"], 3, "Expected three cutters.") // Test that the sort worked. 0.2 should be at the first index for Cutter. - if v := i.Entries["cutter"][0].Version; v != "0.2.0" { - t.Errorf("Unexpected first version: %s", v) - } + v := i.Entries["cutter"][0].Version + assert.Equalf(t, "0.2.0", v, "Unexpected first version: %s", v) cv, err := i.Get("setter", "0.1.9") if err == nil && !strings.Contains(cv.Version, "0.1.9") { @@ -129,14 +121,12 @@ func TestIndexFile(t *testing.T) { } cv, err = i.Get("setter", "0.1.9+alpha") - if err != nil || cv.Version != "0.1.9+alpha" { - t.Error("Expected version: 0.1.9+alpha") - } + require.NoError(t, err, "Expected version: 0.1.9+alpha") + assert.Equal(t, "0.1.9+alpha", cv.Version, "Expected version: 0.1.9+alpha") cv, err = i.Get("setter", "0.1.8") - if err != nil || cv.Version != "0.1.8" { - t.Error("Expected version: 0.1.8") - } + require.NoError(t, err, "Expected version: 0.1.8") + assert.Equal(t, "0.1.8", cv.Version, "Expected version: 0.1.8") } func TestLoadIndex(t *testing.T) { @@ -162,9 +152,7 @@ func TestLoadIndex(t *testing.T) { t.Run(tc.Name, func(t *testing.T) { t.Parallel() i, err := LoadIndexFile(tc.Filename) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) verifyLocalIndex(t, i) }) } @@ -172,52 +160,39 @@ func TestLoadIndex(t *testing.T) { // TestLoadIndex_Duplicates is a regression to make sure that we don't non-deterministically allow duplicate packages. func TestLoadIndex_Duplicates(t *testing.T) { - if _, err := loadIndex([]byte(indexWithDuplicates), "indexWithDuplicates"); err == nil { - t.Error("Expected an error when duplicate entries are present") - } + _, err := loadIndex([]byte(indexWithDuplicates), "indexWithDuplicates") + assert.Error(t, err, "Expected an error when duplicate entries are present") } func TestLoadIndex_EmptyEntry(t *testing.T) { - if _, err := loadIndex([]byte(indexWithEmptyEntry), "indexWithEmptyEntry"); err != nil { - t.Errorf("unexpected error: %s", err) - } + _, err := loadIndex([]byte(indexWithEmptyEntry), "indexWithEmptyEntry") + assert.NoError(t, err) } func TestLoadIndex_Empty(t *testing.T) { - if _, err := loadIndex([]byte(""), "indexWithEmpty"); err == nil { - t.Error("Expected an error when index.yaml is empty.") - } + _, err := loadIndex([]byte(""), "indexWithEmpty") + assert.Error(t, err, "Expected an error when index.yaml is empty.") } func TestLoadIndexFileAnnotations(t *testing.T) { i, err := LoadIndexFile(annotationstestfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) verifyLocalIndex(t, i) - if len(i.Annotations) != 1 { - t.Fatalf("Expected 1 annotation but got %d", len(i.Annotations)) - } - if i.Annotations["helm.sh/test"] != "foo bar" { - t.Error("Did not get expected value for helm.sh/test annotation") - } + require.Lenf(t, i.Annotations, 1, "Expected 1 annotation but got %d", len(i.Annotations)) + assert.Equal(t, "foo bar", i.Annotations["helm.sh/test"], "Did not get expected value for helm.sh/test annotation") } func TestLoadUnorderedIndex(t *testing.T) { i, err := LoadIndexFile(unorderedTestfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) verifyLocalIndex(t, i) } func TestMerge(t *testing.T) { ind1 := NewIndexFile() - if err := ind1.MustAdd(&chart.Metadata{APIVersion: "v2", Name: "dreadnought", Version: "0.1.0"}, "dreadnought-0.1.0.tgz", "http://example.com", "aaaa"); err != nil { - t.Fatalf("unexpected error: %s", err) - } + require.NoError(t, ind1.MustAdd(&chart.Metadata{APIVersion: "v2", Name: "dreadnought", Version: "0.1.0"}, "dreadnought-0.1.0.tgz", "http://example.com", "aaaa")) ind2 := NewIndexFile() @@ -230,121 +205,89 @@ func TestMerge(t *testing.T) { {&chart.Metadata{APIVersion: "v2", Name: "dreadnought", Version: "0.2.0"}, "dreadnought-0.2.0.tgz", "http://example.com", "aaaabbbb"}, {&chart.Metadata{APIVersion: "v2", Name: "doughnut", Version: "0.2.0"}, "doughnut-0.2.0.tgz", "http://example.com", "ccccbbbb"}, } { - if err := ind2.MustAdd(x.md, x.filename, x.baseURL, x.digest); err != nil { - t.Errorf("unexpected error: %s", err) - } + require.NoError(t, ind2.MustAdd(x.md, x.filename, x.baseURL, x.digest)) } ind1.Merge(ind2) - if len(ind1.Entries) != 2 { - t.Errorf("Expected 2 entries, got %d", len(ind1.Entries)) - } + assert.Lenf(t, ind1.Entries, 2, "Expected 2 entries, got %d", len(ind1.Entries)) vs := ind1.Entries["dreadnought"] - if len(vs) != 2 { - t.Errorf("Expected 2 versions, got %d", len(vs)) - } + assert.Lenf(t, vs, 2, "Expected 2 versions, got %d", len(vs)) - if v := vs[1]; v.Version != "0.2.0" { - t.Errorf("Expected %q version to be 0.2.0, got %s", v.Name, v.Version) - } + v := vs[1] + assert.Equalf(t, "0.2.0", v.Version, "Expected %q version to be 0.2.0, got %s", v.Name, v.Version) } func TestDownloadIndexFile(t *testing.T) { t.Run("should download index file", func(t *testing.T) { srv, err := startLocalServerForTests(nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer srv.Close() r, err := NewChartRepository(&Entry{ Name: testRepo, URL: srv.URL, }, getter.All(&cli.EnvSettings{})) - if err != nil { - t.Errorf("Problem creating chart repository from %s: %v", testRepo, err) - } + require.NoErrorf(t, err, "Problem creating chart repository from %s", testRepo) idx, err := r.DownloadIndexFile() - if err != nil { - t.Fatalf("Failed to download index file to %s: %#v", idx, err) - } + require.NoErrorf(t, err, "Failed to download index file to %s", idx) - if _, err := os.Stat(idx); err != nil { - t.Fatalf("error finding created index file: %#v", err) - } + _, err = os.Stat(idx) + + require.NoErrorf(t, err, "error finding created index file") i, err := LoadIndexFile(idx) - if err != nil { - t.Fatalf("Index %q failed to parse: %s", testfile, err) - } + require.NoErrorf(t, err, "Index %q failed to parse", testfile) verifyLocalIndex(t, i) // Check that charts file is also created idx = filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)) - if _, err := os.Stat(idx); err != nil { - t.Fatalf("error finding created charts file: %#v", err) - } + _, err = os.Stat(idx) + require.NoErrorf(t, err, "error finding created charts file") b, err := os.ReadFile(idx) - if err != nil { - t.Fatalf("error reading charts file: %#v", err) - } + require.NoErrorf(t, err, "error reading charts file") verifyLocalChartsFile(t, b, i) }) t.Run("should not decode the path in the repo url while downloading index", func(t *testing.T) { chartRepoURLPath := "/some%2Fpath/test" fileBytes, err := os.ReadFile("testdata/local-index.yaml") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.RawPath == chartRepoURLPath+"/index.yaml" { w.Write(fileBytes) } }) srv, err := startLocalServerForTests(handler) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer srv.Close() r, err := NewChartRepository(&Entry{ Name: testRepo, URL: srv.URL + chartRepoURLPath, }, getter.All(&cli.EnvSettings{})) - if err != nil { - t.Errorf("Problem creating chart repository from %s: %v", testRepo, err) - } + require.NoErrorf(t, err, "Problem creating chart repository from %s", testRepo) idx, err := r.DownloadIndexFile() - if err != nil { - t.Fatalf("Failed to download index file to %s: %#v", idx, err) - } + require.NoErrorf(t, err, "Failed to download index file to %s", idx) - if _, err := os.Stat(idx); err != nil { - t.Fatalf("error finding created index file: %#v", err) - } + _, err = os.Stat(idx) + require.NoErrorf(t, err, "error finding created index file") i, err := LoadIndexFile(idx) - if err != nil { - t.Fatalf("Index %q failed to parse: %s", testfile, err) - } + require.NoErrorf(t, err, "Index %q failed to parse", testfile) verifyLocalIndex(t, i) // Check that charts file is also created idx = filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)) - if _, err := os.Stat(idx); err != nil { - t.Fatalf("error finding created charts file: %#v", err) - } + _, err = os.Stat(idx) + require.NoErrorf(t, err, "error finding created charts file") b, err := os.ReadFile(idx) - if err != nil { - t.Fatalf("error reading charts file: %#v", err) - } + require.NoErrorf(t, err, "error reading charts file") verifyLocalChartsFile(t, b, i) }) } @@ -352,18 +295,13 @@ func TestDownloadIndexFile(t *testing.T) { func verifyLocalIndex(t *testing.T, i *IndexFile) { t.Helper() numEntries := len(i.Entries) - if numEntries != 3 { - t.Errorf("Expected 3 entries in index file but got %d", numEntries) - } + assert.Equalf(t, 3, numEntries, "Expected 3 entries in index file but got %d", numEntries) alpine, ok := i.Entries["alpine"] - if !ok { - t.Fatal("'alpine' section not found.") - } + require.True(t, ok, "'alpine' section not found.") - if l := len(alpine); l != 1 { - t.Fatalf("'alpine' should have 1 chart, got %d", l) - } + l := len(alpine) + require.Equalf(t, 1, l, "'alpine' should have 1 chart, got %d", l) nginx, ok := i.Entries["nginx"] if !ok || len(nginx) != 2 { @@ -419,31 +357,17 @@ func verifyLocalIndex(t *testing.T, i *IndexFile) { for i, tt := range tests { expect := expects[i] - if tt.Name != expect.Name { - t.Errorf("Expected name %q, got %q", expect.Name, tt.Name) - } - if tt.Description != expect.Description { - t.Errorf("Expected description %q, got %q", expect.Description, tt.Description) - } - if tt.Version != expect.Version { - t.Errorf("Expected version %q, got %q", expect.Version, tt.Version) - } - if tt.Digest != expect.Digest { - t.Errorf("Expected digest %q, got %q", expect.Digest, tt.Digest) - } - if tt.Home != expect.Home { - t.Errorf("Expected home %q, got %q", expect.Home, tt.Home) - } + assert.Equalf(t, expect.Name, tt.Name, "Expected name %q, got %q", expect.Name, tt.Name) + assert.Equalf(t, expect.Description, tt.Description, "Expected description %q, got %q", expect.Description, tt.Description) + assert.Equalf(t, expect.Version, tt.Version, "Expected version %q, got %q", expect.Version, tt.Version) + assert.Equalf(t, expect.Digest, tt.Digest, "Expected digest %q, got %q", expect.Digest, tt.Digest) + assert.Equalf(t, expect.Home, tt.Home, "Expected home %q, got %q", expect.Home, tt.Home) for i, url := range tt.URLs { - if url != expect.URLs[i] { - t.Errorf("Expected URL %q, got %q", expect.URLs[i], url) - } + assert.Equalf(t, expect.URLs[i], url, "Expected URL %q, got %q", expect.URLs[i], url) } for i, kw := range tt.Keywords { - if kw != expect.Keywords[i] { - t.Errorf("Expected keywords %q, got %q", expect.Keywords[i], kw) - } + assert.Equalf(t, expect.Keywords[i], kw, "Expected keywords %q, got %q", expect.Keywords[i], kw) } } } @@ -462,21 +386,16 @@ func verifyLocalChartsFile(t *testing.T, chartsContent []byte, indexContent *Ind } sort.Strings(reald) - if strings.Join(expected, " ") != strings.Join(reald, " ") { - t.Errorf("Cached charts file content unexpected. Expected:\n%s\ngot:\n%s", expected, reald) - } + assert.Equalf(t, strings.Join(expected, " "), strings.Join(reald, " "), "Cached charts file content unexpected. Expected:\n%s\ngot:\n%s", expected, reald) } func TestIndexDirectory(t *testing.T) { dir := "testdata/repository" index, err := IndexDirectory(dir, "http://localhost:8080") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if l := len(index.Entries); l != 3 { - t.Fatalf("Expected 3 entries, got %d", l) - } + l := len(index.Entries) + require.Equalf(t, 3, l, "Expected 3 entries, got %d", l) // Other things test the entry generation more thoroughly. We just test a // few fields. @@ -489,20 +408,12 @@ func TestIndexDirectory(t *testing.T) { for _, test := range corpus { cname := test.chartName frobs, ok := index.Entries[cname] - if !ok { - t.Fatalf("Could not read chart %s", cname) - } + require.Truef(t, ok, "Could not read chart %s", cname) frob := frobs[0] - if frob.Digest == "" { - t.Errorf("Missing digest of file %s.", frob.Name) - } - if frob.URLs[0] != test.downloadLink { - t.Errorf("Unexpected URLs: %v", frob.URLs) - } - if frob.Name != cname { - t.Errorf("Expected %q, got %q", cname, frob.Name) - } + assert.NotEmptyf(t, frob.Digest, "Missing digest of file %s.", frob.Name) + assert.Equalf(t, test.downloadLink, frob.URLs[0], "Unexpected URLs: %v", frob.URLs) + assert.Equalf(t, cname, frob.Name, "Expected %q, got %q", cname, frob.Name) } } @@ -520,64 +431,40 @@ func TestIndexAdd(t *testing.T) { {&chart.Metadata{APIVersion: "v2", Name: "alpine", Version: "0.1.0"}, "/home/charts/alpine-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"}, {&chart.Metadata{APIVersion: "v2", Name: "deis", Version: "0.1.0"}, "/home/charts/deis-0.1.0.tgz", "http://example.com/charts/", "sha256:1234567890"}, } { - if err := i.MustAdd(x.md, x.filename, x.baseURL, x.digest); err != nil { - t.Errorf("unexpected error adding to index: %s", err) - } + require.NoErrorf(t, i.MustAdd(x.md, x.filename, x.baseURL, x.digest), "unexpected error adding to index") } - if i.Entries["clipper"][0].URLs[0] != "http://example.com/charts/clipper-0.1.0.tgz" { - t.Errorf("Expected http://example.com/charts/clipper-0.1.0.tgz, got %s", i.Entries["clipper"][0].URLs[0]) - } - if i.Entries["alpine"][0].URLs[0] != "http://example.com/charts/alpine-0.1.0.tgz" { - t.Errorf("Expected http://example.com/charts/alpine-0.1.0.tgz, got %s", i.Entries["alpine"][0].URLs[0]) - } - if i.Entries["deis"][0].URLs[0] != "http://example.com/charts/deis-0.1.0.tgz" { - t.Errorf("Expected http://example.com/charts/deis-0.1.0.tgz, got %s", i.Entries["deis"][0].URLs[0]) - } + assert.Equalf(t, "http://example.com/charts/clipper-0.1.0.tgz", i.Entries["clipper"][0].URLs[0], "Expected http://example.com/charts/clipper-0.1.0.tgz, got %s", i.Entries["clipper"][0].URLs[0]) + assert.Equalf(t, "http://example.com/charts/alpine-0.1.0.tgz", i.Entries["alpine"][0].URLs[0], "Expected http://example.com/charts/alpine-0.1.0.tgz, got %s", i.Entries["alpine"][0].URLs[0]) + assert.Equalf(t, "http://example.com/charts/deis-0.1.0.tgz", i.Entries["deis"][0].URLs[0], "Expected http://example.com/charts/deis-0.1.0.tgz, got %s", i.Entries["deis"][0].URLs[0]) // test error condition - if err := i.MustAdd(&chart.Metadata{}, "error-0.1.0.tgz", "", ""); err == nil { - t.Fatal("expected error adding to index") - } + require.Error(t, i.MustAdd(&chart.Metadata{}, "error-0.1.0.tgz", "", ""), "expected error adding to index") } func TestIndexWrite(t *testing.T) { i := NewIndexFile() - if err := i.MustAdd(&chart.Metadata{APIVersion: "v2", Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"); err != nil { - t.Fatalf("unexpected error: %s", err) - } + require.NoError(t, i.MustAdd(&chart.Metadata{APIVersion: "v2", Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890")) dir := t.TempDir() testpath := filepath.Join(dir, "test") i.WriteFile(testpath, 0o600) got, err := os.ReadFile(testpath) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(string(got), "clipper-0.1.0.tgz") { - t.Fatal("Index files doesn't contain expected content") - } + require.NoError(t, err) + require.Contains(t, string(got), "clipper-0.1.0.tgz", "Index files doesn't contain expected content") } func TestIndexJSONWrite(t *testing.T) { i := NewIndexFile() - if err := i.MustAdd(&chart.Metadata{APIVersion: "v2", Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"); err != nil { - t.Fatalf("unexpected error: %s", err) - } + require.NoError(t, i.MustAdd(&chart.Metadata{APIVersion: "v2", Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890")) dir := t.TempDir() testpath := filepath.Join(dir, "test") i.WriteJSONFile(testpath, 0o600) got, err := os.ReadFile(testpath) - if err != nil { - t.Fatal(err) - } - if !json.Valid(got) { - t.Fatal("Index files doesn't contain valid JSON") - } - if !strings.Contains(string(got), "clipper-0.1.0.tgz") { - t.Fatal("Index files doesn't contain expected content") - } + require.NoError(t, err) + require.True(t, json.Valid(got), "Index files doesn't contain valid JSON") + require.Contains(t, string(got), "clipper-0.1.0.tgz", "Index files doesn't contain expected content") } func TestAddFileIndexEntriesNil(t *testing.T) { @@ -592,9 +479,7 @@ func TestAddFileIndexEntriesNil(t *testing.T) { }{ {&chart.Metadata{APIVersion: "v2", Name: " ", Version: "8033-5.apinie+s.r"}, "setter-0.1.9+beta.tgz", "http://example.com/charts", "sha256:1234567890abc"}, } { - if err := i.MustAdd(x.md, x.filename, x.baseURL, x.digest); err == nil { - t.Error("expected err to be non-nil when entries not initialized") - } + assert.Error(t, i.MustAdd(x.md, x.filename, x.baseURL, x.digest), "expected err to be non-nil when entries not initialized") } } @@ -624,22 +509,16 @@ func TestIgnoreSkippableChartValidationError(t *testing.T) { result := ignoreSkippableChartValidationError(tc.Input) if tc.Input == nil { - if result != nil { - t.Error("expected nil result for nil input") - } + assert.NoError(t, result, "expected nil result for nil input") return } if tc.ErrorSkipped { - if result != nil { - t.Error("expected nil result for skipped error") - } + assert.NoError(t, result, "expected nil result for skipped error") return } - if !errors.Is(tc.Input, result) { - t.Error("expected the result equal to input") - } + assert.ErrorIs(t, tc.Input, result, "expected the result equal to input") }) } } @@ -700,17 +579,11 @@ func TestLoadIndex_DuplicateChartDeps(t *testing.T) { for _, tc := range tests { t.Run(tc.source, func(t *testing.T) { idx, err := loadIndex([]byte(tc.data), tc.source) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } + require.NoError(t, err) cvs := idx.Entries["nginx"] - if cvs == nil { - t.Error("expected one chart version not to be filtered out") - } + assert.NotNil(t, cvs, "expected one chart version not to be filtered out") for _, v := range cvs { - if v.Name == "alpine" { - t.Error("malformed version was not filtered out") - } + assert.NotEqual(t, "alpine", v.Name, "malformed version was not filtered out") } }) } @@ -751,9 +624,7 @@ func TestIsVersionRange(t *testing.T) { for _, tt := range tests { t.Run(tt.version, func(t *testing.T) { got := isVersionRange(tt.version) - if got != tt.expected { - t.Errorf("isVersionRange(%q) = %v, want %v", tt.version, got, tt.expected) - } + assert.Equalf(t, tt.expected, got, "isVersionRange(%q) = %v, want %v", tt.version, got, tt.expected) }) } } From 658a8fac156a92a7462cc9aab98da796da6dc116 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:57 +0200 Subject: [PATCH 458/571] chore(pkg): refactor: convert tests to testify assert/require part 4 refactor: convert tests to testify assert/require in pkg Signed-off-by: Matthieu MOREL --- pkg/chart/v2/loader/load_test.go | 322 ++++++++---------------------- pkg/storage/driver/memory_test.go | 93 +++------ 2 files changed, 116 insertions(+), 299 deletions(-) diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index 46d17e899..da5c3b725 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -31,6 +31,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/chart/common" @@ -40,13 +41,9 @@ import ( func TestLoadDir(t *testing.T) { l, err := Loader("testdata/frobnitz") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyFrobnitz(t, c) verifyChart(t, c) verifyDependencies(t, c) @@ -59,33 +56,24 @@ func TestLoadDirWithDevNull(t *testing.T) { } l, err := Loader("testdata/frobnitz_with_dev_null") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } - if _, err := l.Load(); err == nil { - t.Error("packages with an irregular file (/dev/null) should not load") - } + require.NoError(t, err, "Failed to load testdata") + _, err = l.Load() + assert.Error(t, err, "packages with an irregular file (/dev/null) should not load") } func TestLoadDirWithSymlink(t *testing.T) { sym := filepath.Join("..", "LICENSE") link := filepath.Join("testdata", "frobnitz_with_symlink", "LICENSE") - if err := os.Symlink(sym, link); err != nil { - t.Fatal(err) - } + require.NoError(t, os.Symlink(sym, link)) defer os.Remove(link) l, err := Loader("testdata/frobnitz_with_symlink") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyFrobnitz(t, c) verifyChart(t, c) verifyDependencies(t, c) @@ -102,18 +90,13 @@ func TestBomTestData(t *testing.T) { } archive, err := os.ReadFile("testdata/frobnitz_with_bom.tgz") - if err != nil { - t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) - } + require.NoError(t, err, "Error reading archive frobnitz_with_bom.tgz") unzipped, err := gzip.NewReader(bytes.NewReader(archive)) - if err != nil { - t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) - } + require.NoError(t, err, "Error reading archive frobnitz_with_bom.tgz") defer unzipped.Close() for _, testFile := range testFiles { data := make([]byte, 3) - err := unzipped.Reset(bytes.NewReader(archive)) - require.NoErrorf(t, err, "Error reading archive frobnitz_with_bom.tgz") + require.NoErrorf(t, unzipped.Reset(bytes.NewReader(archive)), "Error reading archive frobnitz_with_bom.tgz") tr := tar.NewReader(unzipped) for { file, err := tr.Next() @@ -135,13 +118,9 @@ func TestBomTestData(t *testing.T) { func TestLoadDirWithUTFBOM(t *testing.T) { l, err := Loader("testdata/frobnitz_with_bom") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyFrobnitz(t, c) verifyChart(t, c) verifyDependencies(t, c) @@ -151,13 +130,9 @@ func TestLoadDirWithUTFBOM(t *testing.T) { func TestLoadArchiveWithUTFBOM(t *testing.T) { l, err := Loader("testdata/frobnitz_with_bom.tgz") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyFrobnitz(t, c) verifyChart(t, c) verifyDependencies(t, c) @@ -167,39 +142,27 @@ func TestLoadArchiveWithUTFBOM(t *testing.T) { func TestLoadV1(t *testing.T) { l, err := Loader("testdata/frobnitz.v1") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyDependencies(t, c) verifyDependenciesLock(t, c) } func TestLoadFileV1(t *testing.T) { l, err := Loader("testdata/frobnitz.v1.tgz") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyDependencies(t, c) verifyDependenciesLock(t, c) } func TestLoadFile(t *testing.T) { l, err := Loader("testdata/frobnitz-1.2.3.tgz") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyFrobnitz(t, c) verifyChart(t, c) verifyDependencies(t, c) @@ -223,12 +186,8 @@ func TestLoadFiles_BadCases(t *testing.T) { expectError: "validation: chart.metadata.apiVersion is required"}, } { _, err := LoadFiles(tt.bufferedFiles) - if err == nil { - t.Fatal("expected error when load illegal files") - } - if !strings.Contains(err.Error(), tt.expectError) { - t.Errorf("Expected error to contain %q, got %q for %s", tt.expectError, err.Error(), tt.name) - } + require.Error(t, err, "expected error when load illegal files") + assert.ErrorContains(t, err, tt.expectError, "Expected error to contain %q, got %q for %s", tt.expectError, err.Error(), tt.name) } } @@ -280,36 +239,16 @@ icon: https://example.com/64x64.png } c, err := LoadFiles(goodFiles) - if err != nil { - t.Errorf("Expected good files to be loaded, got %v", err) - } - - if c.Name() != "frobnitz" { - t.Errorf("Expected chart name to be 'frobnitz', got %s", c.Name()) - } - - if c.Values["var"] != "some values" { - t.Error("Expected chart values to be populated with default values") - } - - if len(c.Raw) != 5 { - t.Errorf("Expected %d files, got %d", 5, len(c.Raw)) - } - - if !bytes.Equal(c.Schema, []byte("type: Values")) { - t.Error("Expected chart schema to be populated with default values") - } - - if len(c.Templates) != 2 { - t.Errorf("Expected number of templates == 2, got %d", len(c.Templates)) - } - - if _, err = LoadFiles([]*archive.BufferedFile{}); err == nil { - t.Fatal("Expected err to be non-nil") - } - if err.Error() != "Chart.yaml file is missing" { - t.Errorf("Expected chart metadata missing error, got '%s'", err.Error()) - } + require.NoError(t, err, "Expected good files to be loaded") + assert.Equal(t, "frobnitz", c.Name(), "Expected chart name to be 'frobnitz', got %s", c.Name()) + assert.Equal(t, "some values", c.Values["var"], "Expected chart values to be populated with default values") + assert.Len(t, c.Raw, 5, "Expected %d files, got %d", 5, len(c.Raw)) + assert.True(t, bytes.Equal(c.Schema, []byte("type: Values")), "Expected chart schema to be populated with default values") + assert.Len(t, c.Templates, 2, "Expected number of templates == 2, got %d", len(c.Templates)) + + _, err = LoadFiles([]*archive.BufferedFile{}) + require.Error(t, err, "Expected err to be non-nil") + assert.EqualError(t, err, "Chart.yaml file is missing") } // Test the order of file loading. The Chart.yaml file needs to come first for @@ -365,9 +304,7 @@ icon: https://example.com/64x64.png // Capture stderr to make sure message about Chart.yaml handle dependencies // is not present r, w, err := os.Pipe() - if err != nil { - t.Fatalf("Unable to create pipe: %s", err) - } + require.NoError(t, err, "Unable to create pipe") stderr := log.Writer() log.SetOutput(w) defer func() { @@ -375,25 +312,19 @@ icon: https://example.com/64x64.png }() _, err = LoadFiles(goodFiles) - if err != nil { - t.Errorf("Expected good files to be loaded, got %v", err) - } + require.NoError(t, err, "Expected good files to be loaded") w.Close() var text bytes.Buffer io.Copy(&text, r) - if text.String() != "" { - t.Errorf("Expected no message to Stderr, got %s", text.String()) - } + assert.Empty(t, text.String(), "Expected no message to Stderr, got %s", text.String()) } // Packaging the chart on a Windows machine will produce an // archive that has \\ as delimiters. Test that we support these archives func TestLoadFileBackslash(t *testing.T) { c, err := Load("testdata/frobnitz_backslash-1.2.3.tgz") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyChartFileAndTemplate(t, c, "frobnitz_backslash") verifyChart(t, c) verifyDependencies(t, c) @@ -401,13 +332,9 @@ func TestLoadFileBackslash(t *testing.T) { func TestLoadV2WithReqs(t *testing.T) { l, err := Loader("testdata/frobnitz.v2.reqs") - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") c, err := l.Load() - if err != nil { - t.Fatalf("Failed to load testdata: %s", err) - } + require.NoError(t, err, "Failed to load testdata") verifyDependencies(t, c) verifyDependenciesLock(t, c) } @@ -417,9 +344,7 @@ func TestLoadInvalidArchive(t *testing.T) { writeTar := func(filename, internalPath string, body []byte) { dest, err := os.Create(filename) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) zipper := gzip.NewWriter(dest) tw := tar.NewWriter(zipper) @@ -429,12 +354,9 @@ func TestLoadInvalidArchive(t *testing.T) { Size: int64(len(body)), ModTime: time.Now(), } - if err := tw.WriteHeader(h); err != nil { - t.Fatal(err) - } - if _, err := tw.Write(body); err != nil { - t.Fatal(err) - } + require.NoError(t, tw.WriteHeader(h)) + _, err = tw.Write(body) + require.NoError(t, err) tw.Close() zipper.Close() dest.Close() @@ -466,37 +388,27 @@ func TestLoadInvalidArchive(t *testing.T) { illegalChart := filepath.Join(tmpdir, tt.chartname) writeTar(illegalChart, tt.internal, []byte("hello: world")) _, err := Load(illegalChart) - if err == nil { - t.Fatal("expected error when unpacking illegal files") - } - if !strings.Contains(err.Error(), tt.expectError) { - t.Errorf("Expected error to contain %q, got %q for %s", tt.expectError, err.Error(), tt.chartname) - } + require.Error(t, err, "expected error when unpacking illegal files") + require.ErrorContains(t, err, tt.expectError, "Expected error to contain %q, got %q for %s", tt.expectError, err.Error(), tt.chartname) } // Make sure that absolute path gets interpreted as relative illegalChart := filepath.Join(tmpdir, "abs-path.tgz") writeTar(illegalChart, "/Chart.yaml", []byte("hello: world")) _, err := Load(illegalChart) - if err.Error() != "validation: chart.metadata.name is required" { - t.Error(err) - } + require.EqualError(t, err, "validation: chart.metadata.name is required") // And just to validate that the above was not spurious illegalChart = filepath.Join(tmpdir, "abs-path2.tgz") writeTar(illegalChart, "files/whatever.yaml", []byte("hello: world")) _, err = Load(illegalChart) - if err.Error() != "Chart.yaml file is missing" { - t.Errorf("Unexpected error message: %s", err) - } + require.EqualError(t, err, "Chart.yaml file is missing") // Finally, test that drive letter gets stripped off on Windows illegalChart = filepath.Join(tmpdir, "abs-winpath.tgz") writeTar(illegalChart, "c:\\Chart.yaml", []byte("hello: world")) _, err = Load(illegalChart) - if err.Error() != "validation: chart.metadata.name is required" { - t.Error(err) - } + assert.EqualError(t, err, "validation: chart.metadata.name is required") } func TestLoadValues(t *testing.T) { @@ -543,12 +455,8 @@ foo: for testName, testCase := range testCases { t.Run(testName, func(tt *testing.T) { values, err := LoadValues(bytes.NewReader(testCase.data)) - if err != nil { - tt.Fatal(err) - } - if !reflect.DeepEqual(values, testCase.expctedValues) { - tt.Errorf("Expected values: %v, got %v", testCase.expctedValues, values) - } + require.NoError(tt, err) + assert.Truef(t, reflect.DeepEqual(values, testCase.expctedValues), "Expected values: %v, got %v", testCase.expctedValues, values) }) } } @@ -577,21 +485,15 @@ func TestMergeValuesV2(t *testing.T) { testMap := MergeMaps(flatMap, nestedMap) equal := reflect.DeepEqual(testMap, nestedMap) - if !equal { - t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) - } + assert.True(t, equal, "Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) testMap = MergeMaps(nestedMap, flatMap) equal = reflect.DeepEqual(testMap, flatMap) - if !equal { - t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) - } + assert.True(t, equal, "Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) testMap = MergeMaps(nestedMap, anotherNestedMap) equal = reflect.DeepEqual(testMap, anotherNestedMap) - if !equal { - t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) - } + assert.True(t, equal, "Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) testMap = MergeMaps(anotherFlatMap, anotherNestedMap) expectedMap := map[string]any{ @@ -603,20 +505,14 @@ func TestMergeValuesV2(t *testing.T) { }, } equal = reflect.DeepEqual(testMap, expectedMap) - if !equal { - t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) - } + assert.True(t, equal, "Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) } func verifyChart(t *testing.T, c *chart.Chart) { t.Helper() - if c.Name() == "" { - t.Fatalf("No chart metadata found on %v", c) - } + require.NotEmpty(t, c.Name(), "No chart metadata found on %v", c) t.Logf("Verifying chart %s", c.Name()) - if len(c.Templates) != 1 { - t.Errorf("Expected 1 template, got %d", len(c.Templates)) - } + assert.Len(t, c.Templates, 1, "Expected 1 template, got %d", len(c.Templates)) numfiles := 6 if len(c.Files) != numfiles { @@ -643,62 +539,40 @@ func verifyChart(t *testing.T, c *chart.Chart) { } for _, dep := range c.Dependencies() { - if dep.Metadata == nil { - t.Fatalf("expected metadata on dependency: %v", dep) - } + require.NotNil(t, dep.Metadata, "expected metadata on dependency: %v", dep) exp, ok := expect[dep.Name()] - if !ok { - t.Fatalf("Unknown dependency %s", dep.Name()) - } - if exp["version"] != dep.Metadata.Version { - t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version) - } + require.True(t, ok, "Unknown dependency %s", dep.Name()) + assert.Equal(t, dep.Metadata.Version, exp["version"], "Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version) } } func verifyDependencies(t *testing.T, c *chart.Chart) { t.Helper() - if len(c.Metadata.Dependencies) != 2 { - t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) - } + assert.Len(t, c.Metadata.Dependencies, 2, "Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) tests := []*chart.Dependency{ {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, } for i, tt := range tests { d := c.Metadata.Dependencies[i] - if d.Name != tt.Name { - t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name) - } - if d.Version != tt.Version { - t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version) - } - if d.Repository != tt.Repository { - t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository) - } + assert.Equal(t, tt.Name, d.Name, "Expected dependency named %q, got %q", tt.Name, d.Name) + assert.Equal(t, tt.Version, d.Version, "Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version) + assert.Equal(t, tt.Repository, d.Repository, "Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository) } } func verifyDependenciesLock(t *testing.T, c *chart.Chart) { t.Helper() - if len(c.Metadata.Dependencies) != 2 { - t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) - } + assert.Len(t, c.Metadata.Dependencies, 2, "Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) tests := []*chart.Dependency{ {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, } for i, tt := range tests { d := c.Metadata.Dependencies[i] - if d.Name != tt.Name { - t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name) - } - if d.Version != tt.Version { - t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version) - } - if d.Repository != tt.Repository { - t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository) - } + assert.Equal(t, tt.Name, d.Name, "Expected dependency named %q, got %q", tt.Name, d.Name) + assert.Equal(t, tt.Version, d.Version, "Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version) + assert.Equal(t, tt.Repository, d.Repository, "Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository) } } @@ -709,53 +583,25 @@ func verifyFrobnitz(t *testing.T, c *chart.Chart) { func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) { t.Helper() - if c.Metadata == nil { - t.Fatal("Metadata is nil") - } - if c.Name() != name { - t.Errorf("Expected %s, got %s", name, c.Name()) - } - if len(c.Templates) != 1 { - t.Fatalf("Expected 1 template, got %d", len(c.Templates)) - } - if c.Templates[0].Name != "templates/template.tpl" { - t.Errorf("Unexpected template: %s", c.Templates[0].Name) - } - if len(c.Templates[0].Data) == 0 { - t.Error("No template data.") - } - if len(c.Files) != 6 { - t.Fatalf("Expected 6 Files, got %d", len(c.Files)) - } - if len(c.Dependencies()) != 2 { - t.Fatalf("Expected 2 Dependency, got %d", len(c.Dependencies())) - } - if len(c.Metadata.Dependencies) != 2 { - t.Fatalf("Expected 2 Dependencies.Dependency, got %d", len(c.Metadata.Dependencies)) - } - if len(c.Lock.Dependencies) != 2 { - t.Fatalf("Expected 2 Lock.Dependency, got %d", len(c.Lock.Dependencies)) - } + require.NotNil(t, c.Metadata, "Metadata is nil") + assert.Equal(t, name, c.Name(), "Expected %s, got %s", name, c.Name()) + require.Len(t, c.Templates, 1, "Expected 1 template, got %d", len(c.Templates)) + assert.Equal(t, "templates/template.tpl", c.Templates[0].Name, "Unexpected template: %s", c.Templates[0].Name) + assert.NotEmpty(t, c.Templates[0].Data, "No template data.") + require.Len(t, c.Files, 6, "Expected 6 Files, got %d", len(c.Files)) + require.Len(t, c.Dependencies(), 2, "Expected 2 Dependency, got %d", len(c.Dependencies())) + require.Len(t, c.Metadata.Dependencies, 2, "Expected 2 Dependencies.Dependency, got %d", len(c.Metadata.Dependencies)) + require.Len(t, c.Lock.Dependencies, 2, "Expected 2 Lock.Dependency, got %d", len(c.Lock.Dependencies)) for _, dep := range c.Dependencies() { switch dep.Name() { case "mariner": case "alpine": - if len(dep.Templates) != 1 { - t.Fatalf("Expected 1 template, got %d", len(dep.Templates)) - } - if dep.Templates[0].Name != "templates/alpine-pod.yaml" { - t.Errorf("Unexpected template: %s", dep.Templates[0].Name) - } - if len(dep.Templates[0].Data) == 0 { - t.Error("No template data.") - } - if len(dep.Files) != 1 { - t.Fatalf("Expected 1 Files, got %d", len(dep.Files)) - } - if len(dep.Dependencies()) != 2 { - t.Fatalf("Expected 2 Dependency, got %d", len(dep.Dependencies())) - } + require.Len(t, dep.Templates, 1, "Expected 1 template, got %d", len(dep.Templates)) + assert.Equal(t, "templates/alpine-pod.yaml", dep.Templates[0].Name, "Unexpected template: %s", dep.Templates[0].Name) + assert.NotEmpty(t, dep.Templates[0].Data, "No template data.") + require.Len(t, dep.Files, 1, "Expected 1 Files, got %d", len(dep.Files)) + require.Len(t, dep.Dependencies(), 2, "Expected 2 Dependency, got %d", len(dep.Dependencies())) default: t.Errorf("Unexpected dependency %s", dep.Name()) } @@ -765,8 +611,6 @@ func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) { func verifyBomStripped(t *testing.T, files []*common.File) { t.Helper() for _, file := range files { - if bytes.HasPrefix(file.Data, utf8bom) { - t.Errorf("Byte Order Mark still present in processed file %s", file.Name) - } + assert.Falsef(t, bytes.HasPrefix(file.Data, utf8bom), "Byte Order Mark still present in processed file %s", file.Name) } } diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go index 6040daf59..7a8d4cb3e 100644 --- a/pkg/storage/driver/memory_test.go +++ b/pkg/storage/driver/memory_test.go @@ -21,6 +21,7 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/release" @@ -29,9 +30,8 @@ import ( ) func TestMemoryName(t *testing.T) { - if mem := NewMemory(); mem.Name() != MemoryDriverName { - t.Errorf("Expected name to be %q, got %q", MemoryDriverName, mem.Name()) - } + mem := NewMemory() + assert.Equalf(t, MemoryDriverName, mem.Name(), "Expected name to be %q, got %q", MemoryDriverName, mem.Name()) } func TestMemoryCreate(t *testing.T) { @@ -66,13 +66,12 @@ func TestMemoryCreate(t *testing.T) { for _, tt := range tests { key := testKey(tt.rls.Name, tt.rls.Version) rls := tt.rls + err := ts.Create(key, rls) - if err := ts.Create(key, rls); err != nil { - if !tt.err { - t.Fatalf("failed to create %q: %s", tt.desc, err) - } - } else if tt.err { - t.Fatalf("Did not get expected error for %q\n", tt.desc) + if tt.err { + require.Error(t, err, "Did not get expected error for %q\n", tt.desc) + } else { + require.NoError(t, err, "failed to create %q", tt.desc) } } } @@ -93,12 +92,11 @@ func TestMemoryGet(t *testing.T) { ts := tsFixtureMemory(t) for _, tt := range tests { ts.SetNamespace(tt.namespace) - if _, err := ts.Get(tt.key); err != nil { - if !tt.err { - t.Fatalf("Failed %q to get '%s': %q\n", tt.desc, tt.key, err) - } - } else if tt.err { - t.Fatalf("Did not get expected error for %q '%s'\n", tt.desc, tt.key) + _, err := ts.Get(tt.key) + if tt.err { + require.Error(t, err, "Did not get expected error for %q '%s'\n", tt.desc, tt.key) + } else { + require.NoError(t, err, "Failed %q to get '%s'", tt.desc, tt.key) } } } @@ -113,12 +111,8 @@ func TestMemoryList(t *testing.T) { return rls.Info.Status == common.StatusDeployed }) // check - if err != nil { - t.Errorf("Failed to list deployed releases: %s", err) - } - if len(dpl) != 2 { - t.Errorf("Expected 2 deployed, got %d", len(dpl)) - } + require.NoError(t, err, "Failed to list deployed releases") + assert.Len(t, dpl, 2, "Expected 2 deployed, got %d", len(dpl)) // list all superseded releases ssd, err := ts.List(func(rel release.Releaser) bool { @@ -126,12 +120,8 @@ func TestMemoryList(t *testing.T) { return rls.Info.Status == common.StatusSuperseded }) // check - if err != nil { - t.Errorf("Failed to list superseded releases: %s", err) - } - if len(ssd) != 6 { - t.Errorf("Expected 6 superseded, got %d", len(ssd)) - } + require.NoError(t, err, "Failed to list superseded releases") + assert.Len(t, ssd, 6, "Expected 6 superseded, got %d", len(ssd)) // list all deleted releases del, err := ts.List(func(rel release.Releaser) bool { @@ -139,12 +129,8 @@ func TestMemoryList(t *testing.T) { return rls.Info.Status == common.StatusUninstalled }) // check - if err != nil { - t.Errorf("Failed to list deleted releases: %s", err) - } - if len(del) != 0 { - t.Errorf("Expected 0 deleted, got %d", len(del)) - } + require.NoError(t, err, "Failed to list deleted releases") + assert.Empty(t, del, "Expected 0 deleted, got %d", len(del)) } func TestMemoryQuery(t *testing.T) { @@ -172,13 +158,9 @@ func TestMemoryQuery(t *testing.T) { for _, tt := range tests { ts.SetNamespace(tt.namespace) l, err := ts.Query(tt.lbs) - if err != nil { - t.Fatalf("Failed to query: %s\n", err) - } + require.NoError(t, err, "Failed to query") - if tt.xlen != len(l) { - t.Fatalf("Expected %d results, actual %d\n", tt.xlen, len(l)) - } + require.Equal(t, len(l), tt.xlen, "Expected %d results, actual %d\n", tt.xlen, len(l)) } } @@ -217,23 +199,18 @@ func TestMemoryUpdate(t *testing.T) { ts := tsFixtureMemory(t) for _, tt := range tests { - if err := ts.Update(tt.key, tt.rls); err != nil { - if !tt.err { - t.Fatalf("Failed %q: %s\n", tt.desc, err) - } - continue - } else if tt.err { - t.Fatalf("Did not get expected error for %q '%s'\n", tt.desc, tt.key) - } + err := ts.Update(tt.key, tt.rls) - ts.SetNamespace(tt.rls.Namespace) - r, err := ts.Get(tt.key) - if err != nil { - t.Fatalf("Failed to get: %s\n", err) - } + if tt.err { + require.Error(t, err, "Did not get expected error for %q '%s'\n", tt.desc, tt.key) + } else { + require.NoError(t, err, "Failed %q", tt.desc) + + ts.SetNamespace(tt.rls.Namespace) - if !reflect.DeepEqual(r, tt.rls) { - t.Fatalf("Expected %v, actual %v\n", tt.rls, r) + r, err := ts.Get(tt.key) + require.NoError(t, err, "Failed to get") + require.Truef(t, reflect.DeepEqual(r, tt.rls), "Expected %v, actual %v\n", tt.rls, r) } } } @@ -256,9 +233,7 @@ func TestMemoryDelete(t *testing.T) { ts := tsFixtureMemory(t) ts.SetNamespace("") start, err := ts.Query(map[string]string{"status": "deployed"}) - if err != nil { - t.Errorf("Query failed: %s", err) - } + require.NoError(t, err, "Query failed") startLen := len(start) for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { @@ -280,9 +255,7 @@ func TestMemoryDelete(t *testing.T) { // Make sure that the deleted records are gone. ts.SetNamespace("") end, err := ts.Query(map[string]string{"status": "deployed"}) - if err != nil { - t.Errorf("Query failed: %s", err) - } + require.NoError(t, err, "Query failed") endLen := len(end) if startLen-2 != endLen { From 3ff05c835ecb1bbf794089d309556dbc75778ef7 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:56 +0200 Subject: [PATCH 459/571] chore(pkg): refactor: convert tests to testify assert/require part 5 refactor: convert tests to testify assert/require in pkg Signed-off-by: Matthieu MOREL --- pkg/downloader/cache_test.go | 5 +- pkg/kube/client_test.go | 279 ++++++++++------------------------- pkg/repo/v1/repo_test.go | 100 ++++--------- 3 files changed, 109 insertions(+), 275 deletions(-) diff --git a/pkg/downloader/cache_test.go b/pkg/downloader/cache_test.go index 49a7adc5d..32f014727 100644 --- a/pkg/downloader/cache_test.go +++ b/pkg/downloader/cache_test.go @@ -105,10 +105,9 @@ func TestDiskCache_PutAndGet(t *testing.T) { t.Run("GetDirectory", func(t *testing.T) { dirKey := sha256.Sum256([]byte("i am a directory")) dirPath := cache.fileName(dirKey, CacheChart) - err := os.MkdirAll(dirPath, 0o755) - require.NoError(t, err) + require.NoError(t, os.MkdirAll(dirPath, 0o755)) - _, err = cache.Get(dirKey, CacheChart) + _, err := cache.Get(dirKey, CacheChart) assert.EqualError(t, err, "is a directory") }) } diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 6421a2c2d..4d46d2692 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -316,9 +316,7 @@ func TestCreate(t *testing.T) { list, err := c.Build(objBody(&tc.Pods), false) require.NoError(t, err) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) result, err := c.Create( list, @@ -487,7 +485,7 @@ func TestUpdate(t *testing.T) { data, err := io.ReadAll(req.Body) require.NoError(t, err) - assert.Equal(t, `{}`, string(data)) + assert.JSONEq(t, `{}`, string(data)) } return newResponse(http.StatusOK, &listTarget.Items[0]) @@ -622,15 +620,12 @@ func TestBuild(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // Test for an invalid manifest infos, err := c.Build(tt.reader, false) - if err != nil && !tt.err { - t.Errorf("Got error message when no error should have occurred: %v", err) - } else if err != nil && strings.Contains(err.Error(), "--validate=false") { - t.Error("error message was not scrubbed") - } - - if len(infos) != tt.count { - t.Errorf("expected %d result objects, got %d", tt.count, len(infos)) + if tt.err { + require.ErrorContains(t, err, "--validate=false", "error message was not scrubbed") + } else { + require.NoError(t, err, "Got error message when no error should have occurred") } + assert.Len(t, infos, tt.count, "expected %d result objects, got %d", tt.count, len(infos)) }) } } @@ -661,15 +656,12 @@ func TestBuildTable(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // Test for an invalid manifest infos, err := c.BuildTable(tt.reader, false) - if err != nil && !tt.err { - t.Errorf("Got error message when no error should have occurred: %v", err) - } else if err != nil && strings.Contains(err.Error(), "--validate=false") { - t.Error("error message was not scrubbed") - } - - if len(infos) != tt.count { - t.Errorf("expected %d result objects, got %d", tt.count, len(infos)) + if tt.err { + require.ErrorContains(t, err, "--validate=false", "error message was not scrubbed") + } else { + require.NoError(t, err, "Got error message when no error should have occurred") } + assert.Len(t, infos, tt.count, "expected %d result objects, got %d", tt.count, len(infos)) }) } } @@ -705,21 +697,17 @@ func TestPerform(t *testing.T) { c := newTestClient(t) infos, err := c.Build(tt.reader, false) - if err != nil && err.Error() != tt.errMessage { - t.Errorf("Error while building manifests: %v", err) + if err != nil { + require.EqualErrorf(t, err, tt.errMessage, "Error while building manifests") } err = perform(infos, fn) - if (err != nil) != tt.err { - t.Errorf("expected error: %v, got %v", tt.err, err) - } - if err != nil && err.Error() != tt.errMessage { - t.Errorf("expected error message: %v, got %v", tt.errMessage, err) + assert.Equal(t, tt.err, (err != nil), "expected error: %v", tt.err) + if err != nil { + require.EqualErrorf(t, err, tt.errMessage, "expected error message: %v, got %v", tt.errMessage, err) } - if len(results) != tt.count { - t.Errorf("expected %d result objects, got %d", tt.count, len(results)) - } + assert.Len(t, results, tt.count, "expected %d result objects, got %d", tt.count, len(results)) }) } } @@ -771,9 +759,7 @@ func TestWait(t *testing.T) { return newResponse(http.StatusOK, pod) case p == "/namespaces/default/pods" && m == http.MethodPost: resources, err := c.Build(req.Body, false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) now := time.Now() created = &now return newResponse(http.StatusOK, resources[0].Object) @@ -785,32 +771,19 @@ func TestWait(t *testing.T) { } var err error c.Waiter, err = c.GetWaiterWithOptions(LegacyStrategy) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) resources, err := c.Build(objBody(&podList), false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) result, err := c.Create( resources, ClientCreateOptionServerSideApply(false, false)) - if err != nil { - t.Fatal(err) - } - if len(result.Created) != 3 { - t.Errorf("expected 3 resource created, got %d", len(result.Created)) - } - - if err := c.Wait(resources, time.Second*30); err != nil { - t.Errorf("expected wait without error, got %s", err) - } + require.NoError(t, err) + assert.Len(t, result.Created, 3, "expected 3 resource created, got %d", len(result.Created)) + require.NoErrorf(t, c.Wait(resources, time.Second*30), "expected wait without error") - if time.Since(*created) < time.Second*5 { - t.Errorf("expected to wait at least 5 seconds before ready status was detected, but got %s", time.Since(*created)) - } + assert.GreaterOrEqualf(t, time.Since(*created), time.Second*5, "expected to wait at least 5 seconds before ready status was detected, but got %s", time.Since(*created)) } func TestWaitJob(t *testing.T) { @@ -832,9 +805,7 @@ func TestWaitJob(t *testing.T) { return newResponse(http.StatusOK, job) case p == "/namespaces/default/jobs" && m == http.MethodPost: resources, err := c.Build(req.Body, false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) now := time.Now() created = &now return newResponse(http.StatusOK, resources[0].Object) @@ -846,31 +817,17 @@ func TestWaitJob(t *testing.T) { } var err error c.Waiter, err = c.GetWaiterWithOptions(LegacyStrategy) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) resources, err := c.Build(objBody(job), false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) result, err := c.Create( resources, ClientCreateOptionServerSideApply(false, false)) - if err != nil { - t.Fatal(err) - } - if len(result.Created) != 1 { - t.Errorf("expected 1 resource created, got %d", len(result.Created)) - } - - if err := c.WaitWithJobs(resources, time.Second*30); err != nil { - t.Errorf("expected wait without error, got %s", err) - } - - if time.Since(*created) < time.Second*5 { - t.Errorf("expected to wait at least 5 seconds before ready status was detected, but got %s", time.Since(*created)) - } + require.NoError(t, err) + assert.Len(t, result.Created, 1, "expected 1 resource created, got %d", len(result.Created)) + require.NoErrorf(t, c.WaitWithJobs(resources, time.Second*30), "expected wait without error") + assert.GreaterOrEqualf(t, time.Since(*created), time.Second*5, "expected to wait at least 5 seconds before ready status was detected, but got %s", time.Since(*created)) } func TestWaitDelete(t *testing.T) { @@ -896,9 +853,7 @@ func TestWaitDelete(t *testing.T) { return newResponse(http.StatusOK, &pod) case p == "/namespaces/default/pods" && m == http.MethodPost: resources, err := c.Build(req.Body, false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return newResponse(http.StatusOK, resources[0].Object) default: t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) @@ -908,73 +863,46 @@ func TestWaitDelete(t *testing.T) { } var err error c.Waiter, err = c.GetWaiterWithOptions(LegacyStrategy) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) resources, err := c.Build(objBody(&pod), false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) result, err := c.Create( resources, ClientCreateOptionServerSideApply(false, false)) - if err != nil { - t.Fatal(err) - } - if len(result.Created) != 1 { - t.Errorf("expected 1 resource created, got %d", len(result.Created)) - } - if _, err := c.Delete(resources, metav1.DeletePropagationBackground); err != nil { - t.Fatal(err) - } - - if err := c.WaitForDelete(resources, time.Second*30); err != nil { - t.Errorf("expected wait without error, got %s", err) - } - - if time.Since(*deleted) < time.Second*5 { - t.Errorf("expected to wait at least 5 seconds before ready status was detected, but got %s", time.Since(*deleted)) - } + require.NoError(t, err) + assert.Len(t, result.Created, 1, "expected 1 resource created, got %d", len(result.Created)) + _, errs := c.Delete(resources, metav1.DeletePropagationBackground) + require.Nil(t, errs) + require.NoErrorf(t, c.WaitForDelete(resources, time.Second*30), "expected wait without error") + assert.GreaterOrEqualf(t, time.Since(*deleted), time.Second*5, "expected to wait at least 5 seconds before ready status was detected, but got %s", time.Since(*deleted)) } func TestReal(t *testing.T) { t.Skip("This is a live test, comment this line to run") c := New(nil) resources, err := c.Build(strings.NewReader(guestbookManifest), false) - if err != nil { - t.Fatal(err) - } - if _, err := c.Create(resources); err != nil { - t.Fatal(err) - } + require.NoError(t, err) + _, err = c.Create(resources) + require.NoError(t, err) testSvcEndpointManifest := testServiceManifest + "\n---\n" + testEndpointManifest c = New(nil) resources, err = c.Build(strings.NewReader(testSvcEndpointManifest), false) - if err != nil { - t.Fatal(err) - } - if _, err := c.Create(resources); err != nil { - t.Fatal(err) - } + require.NoError(t, err) + _, err = c.Create(resources) + require.NoError(t, err) resources, err = c.Build(strings.NewReader(testEndpointManifest), false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if _, errs := c.Delete(resources, metav1.DeletePropagationBackground); errs != nil { - t.Fatal(errs) - } + _, errs := c.Delete(resources, metav1.DeletePropagationBackground) + require.Nil(t, errs) resources, err = c.Build(strings.NewReader(testSvcEndpointManifest), false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // ensures that delete does not fail if a resource is not found - if _, errs := c.Delete(resources, metav1.DeletePropagationBackground); errs != nil { - t.Fatal(errs) - } + _, errs = c.Delete(resources, metav1.DeletePropagationBackground) + require.Nil(t, errs) } func TestGetPodList(t *testing.T) { @@ -1004,10 +932,9 @@ func TestOutputContainerLogsForPodList(t *testing.T) { c := Client{Namespace: namespace, kubeClient: kubeClient} outBuffer := &bytes.Buffer{} outBufferFunc := func(_, _, _ string) io.Writer { return outBuffer } - err := c.OutputContainerLogsForPodList(&somePodList, namespace, outBufferFunc) clientAssertions := assert.New(t) req := require.New(t) - req.NoError(err) + req.NoError(c.OutputContainerLogsForPodList(&somePodList, namespace, outBufferFunc)) clientAssertions.Equal("fake logsfake logsfake logs", outBuffer.String()) } @@ -1247,23 +1174,11 @@ func (c createPatchTestCase) run(t *testing.T) { } patch, patchType, err := createPatch(c.original, targetInfo, c.threeWayMergeForUnstructured) - if err != nil { - t.Fatalf("Failed to create patch: %v", err) - } + require.NoError(t, err, "Failed to create patch") - if c.expectedPatch != string(patch) { - t.Errorf("Unexpected patch.\nTarget:\n%s\nOriginal:\n%s\nActual:\n%s\n\nExpected:\n%s\nGot:\n%s", - c.target, - c.original, - c.actual, - c.expectedPatch, - string(patch), - ) - } + assert.Equal(t, c.expectedPatch, string(patch), "Unexpected patch.\nTarget:\n%s\nOriginal:\n%s\nActual:\n%s\n\nExpected:\n%s\nGot:\n%s", c.target, c.original, c.actual, c.expectedPatch, string(patch)) - if patchType != types.MergePatchType { - t.Errorf("Expected patch type %s, got %s", types.MergePatchType, patchType) - } + assert.Equal(t, types.MergePatchType, patchType, "Expected patch type %s, got %s", types.MergePatchType, patchType) } func newTestCustomResourceData(metadata map[string]string, spec map[string]any) *unstructured.Unstructured { @@ -1438,12 +1353,8 @@ func TestIsReachableTwiceAfterClientCreationFailure(t *testing.T) { assertReachableErr := func(label string, err error) { t.Helper() - if err == nil { - t.Fatalf("%s: expected error, got nil", label) - } - if !errors.Is(err, refusedErr) { - t.Fatalf("%s: expected error wrapping %v, got %v", label, refusedErr, err) - } + require.Error(t, err, "%s: expected error, got nil", label) + require.ErrorIs(t, err, refusedErr) } assertReachableErr("first call", client.IsReachable()) @@ -1472,9 +1383,7 @@ func TestIsIncompatibleServerError(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { - if got := isIncompatibleServerError(tc.Err); got != tc.Want { - t.Errorf("isIncompatibleServerError() = %v, want %v", got, tc.Want) - } + assert.Equalf(t, tc.Want, isIncompatibleServerError(tc.Err), "isIncompatibleServerError()") }) } } @@ -1926,9 +1835,7 @@ func TestClientWaitContextCancellationLegacy(t *testing.T) { return newResponse(http.StatusOK, pod) case p == "/namespaces/default/pods" && m == http.MethodPost: resources, err := c.Build(req.Body, false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return newResponse(http.StatusOK, resources[0].Object) default: t.Logf("unexpected request: %s %s", req.Method, req.URL.Path) @@ -1949,10 +1856,7 @@ func TestClientWaitContextCancellationLegacy(t *testing.T) { ClientCreateOptionServerSideApply(false, false)) require.NoError(t, err) assert.Len(t, result.Created, 2, "expected 2 resources created, got %d", len(result.Created)) - - err = c.Wait(resources, time.Second*30) - require.Error(t, err) - assert.ErrorContains(t, err, "context canceled", "expected context canceled error, got: %v", err) + assert.ErrorContains(t, c.Wait(resources, time.Second*30), "context canceled") } func TestClientWaitWithJobsContextCancellationLegacy(t *testing.T) { @@ -1981,9 +1885,7 @@ func TestClientWaitWithJobsContextCancellationLegacy(t *testing.T) { return newResponse(http.StatusOK, job) case p == "/namespaces/default/jobs" && m == http.MethodPost: resources, err := c.Build(req.Body, false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return newResponse(http.StatusOK, resources[0].Object) default: t.Logf("unexpected request: %s %s", req.Method, req.URL.Path) @@ -2004,9 +1906,7 @@ func TestClientWaitWithJobsContextCancellationLegacy(t *testing.T) { ClientCreateOptionServerSideApply(false, false)) require.NoError(t, err) assert.Len(t, result.Created, 1, "expected 1 resource created, got %d", len(result.Created)) - - err = c.WaitWithJobs(resources, time.Second*30) - assert.ErrorContains(t, err, "context canceled", "expected context canceled error, got: %v", err) + assert.ErrorContains(t, c.WaitWithJobs(resources, time.Second*30), "context canceled", "expected context canceled error") } func TestClientWaitForDeleteContextCancellationLegacy(t *testing.T) { @@ -2041,9 +1941,7 @@ func TestClientWaitForDeleteContextCancellationLegacy(t *testing.T) { return newResponse(http.StatusOK, &pod) case p == "/namespaces/default/pods" && m == http.MethodPost: resources, err := c.Build(req.Body, false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return newResponse(http.StatusOK, resources[0].Object) default: t.Logf("unexpected request: %s %s", req.Method, req.URL.Path) @@ -2065,12 +1963,9 @@ func TestClientWaitForDeleteContextCancellationLegacy(t *testing.T) { require.NoError(t, err) assert.Len(t, result.Created, 1, "expected 1 resource created, got %d", len(result.Created)) - if _, err := c.Delete(resources, metav1.DeletePropagationBackground); err != nil { - t.Fatal(err) - } - - err = c.WaitForDelete(resources, time.Second*30) - assert.ErrorContains(t, err, "context canceled", "expected context canceled error, got: %v", err) + _, errs := c.Delete(resources, metav1.DeletePropagationBackground) + require.Nil(t, errs) + assert.ErrorContains(t, c.WaitForDelete(resources, time.Second*30), "context canceled", "expected context canceled error") } func TestClientWaitContextNilDoesNotPanic(t *testing.T) { @@ -2100,9 +1995,7 @@ func TestClientWaitContextNilDoesNotPanic(t *testing.T) { return newResponse(http.StatusOK, pod) case p == "/namespaces/default/pods" && m == http.MethodPost: resources, err := c.Build(req.Body, false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) now := time.Now() created = &now return newResponse(http.StatusOK, resources[0].Object) @@ -2125,10 +2018,7 @@ func TestClientWaitContextNilDoesNotPanic(t *testing.T) { ClientCreateOptionServerSideApply(false, false)) require.NoError(t, err) assert.Len(t, result.Created, 1, "expected 1 resource created, got %d", len(result.Created)) - - err = c.Wait(resources, time.Second*30) - require.NoError(t, err) - + require.NoError(t, c.Wait(resources, time.Second*30)) assert.GreaterOrEqual(t, time.Since(*created), time.Second*2, "expected to wait at least 2 seconds") } @@ -2152,9 +2042,7 @@ func TestClientWaitContextPreCancelledLegacy(t *testing.T) { return newResponse(http.StatusOK, pod) case p == "/namespaces/default/pods" && m == http.MethodPost: resources, err := c.Build(req.Body, false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return newResponse(http.StatusOK, resources[0].Object) default: t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) @@ -2175,9 +2063,7 @@ func TestClientWaitContextPreCancelledLegacy(t *testing.T) { ClientCreateOptionServerSideApply(false, false)) require.NoError(t, err) assert.Len(t, result.Created, 1, "expected 1 resource created, got %d", len(result.Created)) - - err = c.Wait(resources, time.Second*30) - assert.ErrorContains(t, err, "context canceled", "expected context canceled error, got: %v", err) + assert.ErrorContains(t, c.Wait(resources, time.Second*30), "context canceled") } func TestClientWaitContextCancellationStatusWatcher(t *testing.T) { @@ -2202,8 +2088,7 @@ metadata: cancel() - err = c.Wait(resources, time.Second*30) - assert.ErrorContains(t, err, "context canceled", "expected context canceled error, got: %v", err) + assert.ErrorContains(t, c.Wait(resources, time.Second*30), "context canceled", "expected context canceled error") } func TestClientWaitWithJobsContextCancellationStatusWatcher(t *testing.T) { @@ -2228,8 +2113,7 @@ metadata: cancel() - err = c.WaitWithJobs(resources, time.Second*30) - assert.ErrorContains(t, err, "context canceled", "expected context canceled error, got: %v", err) + assert.ErrorContains(t, c.WaitWithJobs(resources, time.Second*30), "context canceled") } func TestClientWaitForDeleteContextCancellationStatusWatcher(t *testing.T) { @@ -2259,8 +2143,7 @@ status: cancel() - err = c.WaitForDelete(resources, time.Second*30) - assert.ErrorContains(t, err, "context canceled", "expected context canceled error, got: %v", err) + assert.ErrorContains(t, c.WaitForDelete(resources, time.Second*30), "context canceled") } // testStatusReader is a custom status reader for testing that returns a configurable status. @@ -2334,8 +2217,7 @@ metadata: // The pod has no Ready condition, but our custom reader returns CurrentStatus, // so the wait should succeed immediately without timeout. - err = c.Wait(resources, time.Second*3) - require.NoError(t, err) + require.NoError(t, c.Wait(resources, time.Second*3)) } func TestClientStatusReadersWithWaitWithJobs(t *testing.T) { @@ -2376,8 +2258,7 @@ metadata: // The job has no Complete condition, but our custom reader returns CurrentStatus, // so the wait should succeed immediately without timeout. - err = c.WaitWithJobs(resources, time.Second*3) - require.NoError(t, err) + require.NoError(t, c.WaitWithJobs(resources, time.Second*3)) } func createManifest(t *testing.T, manifest string, @@ -2385,12 +2266,10 @@ func createManifest(t *testing.T, manifest string, t.Helper() m := make(map[string]any) - err := yaml.Unmarshal([]byte(manifest), &m) - require.NoError(t, err) + require.NoError(t, yaml.Unmarshal([]byte(manifest), &m)) obj := &unstructured.Unstructured{Object: m} gvk := obj.GroupVersionKind() mapping, err := fakeMapper.RESTMapping(gvk.GroupKind(), gvk.Version) require.NoError(t, err) - err = fakeClient.Tracker().Create(mapping.Resource, obj, obj.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(mapping.Resource, obj, obj.GetNamespace())) } diff --git a/pkg/repo/v1/repo_test.go b/pkg/repo/v1/repo_test.go index 960635fc5..e3373fc49 100644 --- a/pkg/repo/v1/repo_test.go +++ b/pkg/repo/v1/repo_test.go @@ -18,8 +18,10 @@ package repo import ( "os" - "strings" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const testRepositoriesFile = "testdata/repositories.yaml" @@ -37,24 +39,14 @@ func TestFile(t *testing.T) { }, ) - if len(rf.Repositories) != 2 { - t.Fatal("Expected 2 repositories") - } + require.Len(t, rf.Repositories, 2, "Expected 2 repositories") - if rf.Has("nosuchrepo") { - t.Error("Found nonexistent repo") - } - if !rf.Has("incubator") { - t.Error("incubator repo is missing") - } + assert.False(t, rf.Has("nosuchrepo"), "Found nonexistent repo") + assert.True(t, rf.Has("incubator"), "incubator repo is missing") stable := rf.Repositories[0] - if stable.Name != "stable" { - t.Error("stable is not named stable") - } - if stable.URL != "https://example.com/stable/charts" { - t.Error("Wrong URL for stable") - } + assert.Equal(t, "stable", stable.Name, "stable is not named stable") + assert.Equal(t, "https://example.com/stable/charts", stable.URL, "Wrong URL for stable") } func TestNewFile(t *testing.T) { @@ -71,22 +63,14 @@ func TestNewFile(t *testing.T) { ) file, err := LoadFile(testRepositoriesFile) - if err != nil { - t.Errorf("%q could not be loaded: %s", testRepositoriesFile, err) - } + require.NoErrorf(t, err, "%q could not be loaded", testRepositoriesFile) - if len(expects.Repositories) != len(file.Repositories) { - t.Fatalf("Unexpected repo data: %#v", file.Repositories) - } + require.Lenf(t, file.Repositories, len(expects.Repositories), "Unexpected repo data: %#v", file.Repositories) for i, expect := range expects.Repositories { got := file.Repositories[i] - if expect.Name != got.Name { - t.Errorf("Expected name %q, got %q", expect.Name, got.Name) - } - if expect.URL != got.URL { - t.Errorf("Expected url %q, got %q", expect.URL, got.URL) - } + assert.Equalf(t, expect.Name, got.Name, "Expected name %q, got %q", expect.Name, got.Name) + assert.Equalf(t, expect.URL, got.URL, "Expected url %q, got %q", expect.URL, got.URL) } } @@ -114,18 +98,12 @@ func TestRepoFile_Get(t *testing.T) { name := "second" entry := repo.Get(name) - if entry == nil { - t.Fatalf("Expected repo entry %q to be found", name) - } + require.NotNilf(t, entry, "Expected repo entry %q to be found", name) - if entry.URL != "https://example.com/second" { - t.Errorf("Expected repo URL to be %q but got %q", "https://example.com/second", entry.URL) - } + assert.Equalf(t, "https://example.com/second", entry.URL, "Expected repo URL to be %q but got %q", "https://example.com/second", entry.URL) entry = repo.Get("nonexistent") - if entry != nil { - t.Errorf("Got unexpected entry %+v", entry) - } + assert.Nilf(t, entry, "Got unexpected entry %+v", entry) } func TestRemoveRepository(t *testing.T) { @@ -143,14 +121,10 @@ func TestRemoveRepository(t *testing.T) { removeRepository := "stable" found := sampleRepository.Remove(removeRepository) - if !found { - t.Errorf("expected repository %s not found", removeRepository) - } + assert.Truef(t, found, "expected repository %s not found", removeRepository) found = sampleRepository.Has(removeRepository) - if found { - t.Errorf("repository %s not deleted", removeRepository) - } + assert.Falsef(t, found, "repository %s not deleted", removeRepository) } func TestUpdateRepository(t *testing.T) { @@ -170,18 +144,14 @@ func TestUpdateRepository(t *testing.T) { URL: "https://example.com/sample", }) - if !sampleRepository.Has(newRepoName) { - t.Errorf("expected repository %s not found", newRepoName) - } + assert.Truef(t, sampleRepository.Has(newRepoName), "expected repository %s not found", newRepoName) repoCount := len(sampleRepository.Repositories) sampleRepository.Update(&Entry{Name: newRepoName, URL: "https://example.com/sample", }) - if repoCount != len(sampleRepository.Repositories) { - t.Errorf("invalid number of repositories found %d, expected number of repositories %d", len(sampleRepository.Repositories), repoCount) - } + assert.Lenf(t, sampleRepository.Repositories, repoCount, "invalid number of repositories found %d, expected number of repositories %d", len(sampleRepository.Repositories), repoCount) } func TestWriteFile(t *testing.T) { @@ -198,31 +168,21 @@ func TestWriteFile(t *testing.T) { ) file, err := os.CreateTemp(t.TempDir(), "helm-repo") - if err != nil { - t.Errorf("failed to create test-file (%v)", err) - } + require.NoErrorf(t, err, "failed to create test-file") defer os.Remove(file.Name()) - if err := sampleRepository.WriteFile(file.Name(), 0o600); err != nil { - t.Errorf("failed to write file (%v)", err) - } + require.NoErrorf(t, sampleRepository.WriteFile(file.Name(), 0o600), "failed to write file") repos, err := LoadFile(file.Name()) - if err != nil { - t.Errorf("failed to load file (%v)", err) - } + require.NoErrorf(t, err, "failed to load file") for _, repo := range sampleRepository.Repositories { - if !repos.Has(repo.Name) { - t.Errorf("expected repository %s not found", repo.Name) - } + assert.Truef(t, repos.Has(repo.Name), "expected repository %s not found", repo.Name) } } func TestRepoNotExists(t *testing.T) { - if _, err := LoadFile("/this/path/does/not/exist.yaml"); err == nil { - t.Error("expected err to be non-nil when path does not exist") - } else if !strings.Contains(err.Error(), "couldn't load repositories file") { - t.Error("expected prompt `couldn't load repositories file`") - } + _, err := LoadFile("/this/path/does/not/exist.yaml") + require.Error(t, err, "expected err to be non-nil when path does not exist") + assert.ErrorContains(t, err, "couldn't load repositories file", "expected prompt `couldn't load repositories file`") } func TestRemoveRepositoryInvalidEntries(t *testing.T) { @@ -246,12 +206,8 @@ func TestRemoveRepositoryInvalidEntries(t *testing.T) { removeRepository := "stable" found := sampleRepository.Remove(removeRepository) - if !found { - t.Errorf("expected repository %s not found", removeRepository) - } + assert.Truef(t, found, "expected repository %s not found", removeRepository) found = sampleRepository.Has(removeRepository) - if found { - t.Errorf("repository %s not deleted", removeRepository) - } + assert.Falsef(t, found, "repository %s not deleted", removeRepository) } From 0844b3b8b513e1ae6787c44fc5cfd224bf86c573 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:56 +0200 Subject: [PATCH 460/571] chore(pkg): refactor: convert tests to testify assert/require part 6 refactor: convert tests to testify assert/require in pkg Signed-off-by: Matthieu MOREL --- pkg/chart/v2/util/dependencies_test.go | 281 +++++++------------------ pkg/registry/chart_test.go | 2 +- pkg/repo/v1/chartrepo_test.go | 127 ++++------- 3 files changed, 110 insertions(+), 300 deletions(-) diff --git a/pkg/chart/v2/util/dependencies_test.go b/pkg/chart/v2/util/dependencies_test.go index 40a17ac3a..2386d2c28 100644 --- a/pkg/chart/v2/util/dependencies_test.go +++ b/pkg/chart/v2/util/dependencies_test.go @@ -21,6 +21,7 @@ import ( "strconv" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/chart/common" @@ -31,9 +32,7 @@ import ( func loadChart(t *testing.T, path string) *chart.Chart { t.Helper() c, err := loader.Load(path) - if err != nil { - t.Fatalf("failed to load testdata: %s", err) - } + require.NoError(t, err, "failed to load testdata") return c } @@ -44,19 +43,11 @@ func TestLoadDependency(t *testing.T) { } check := func(deps []*chart.Dependency) { - if len(deps) != 2 { - t.Errorf("expected 2 dependencies, got %d", len(deps)) - } + assert.Len(t, deps, 2, "expected 2 dependencies, got %d", len(deps)) for i, tt := range tests { - if deps[i].Name != tt.Name { - t.Errorf("expected dependency named %q, got %q", tt.Name, deps[i].Name) - } - if deps[i].Version != tt.Version { - t.Errorf("expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, deps[i].Version) - } - if deps[i].Repository != tt.Repository { - t.Errorf("expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, deps[i].Repository) - } + assert.Equal(t, tt.Name, deps[i].Name, "expected dependency named %q, got %q", tt.Name, deps[i].Name) + assert.Equal(t, tt.Version, deps[i].Version, "expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, deps[i].Version) + assert.Equal(t, tt.Repository, deps[i].Repository, "expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, deps[i].Repository) } } c := loadChart(t, "testdata/frobnitz") @@ -119,18 +110,12 @@ func TestDependencyEnabled(t *testing.T) { for _, tc := range tests { c := loadChart(t, "testdata/subpop") t.Run(tc.name, func(t *testing.T) { - if err := processDependencyEnabled(c, tc.v, ""); err != nil { - t.Fatalf("error processing enabled dependencies %v", err) - } + require.NoErrorf(t, processDependencyEnabled(c, tc.v, ""), "error processing enabled dependencies") names := extractChartNames(c) - if len(names) != len(tc.e) { - t.Fatalf("slice lengths do not match got %v, expected %v", len(names), len(tc.e)) - } + require.Len(t, names, len(tc.e), "slice lengths do not match got %v, expected %v", len(names), len(tc.e)) for i := range names { - if names[i] != tc.e[i] { - t.Fatalf("slice values do not match got %v, expected %v", names, tc.e) - } + require.Equal(t, tc.e[i], names[i], "slice values do not match got %v, expected %v", names, tc.e) } }) } @@ -221,64 +206,44 @@ func TestProcessDependencyImportValues(t *testing.T) { e["SCBexported2A"] = "blaster" e["global.SC1exported2.all.SC1exported3"] = "SC1expstr" - if err := processDependencyImportValues(c, false); err != nil { - t.Fatalf("processing import values dependencies %v", err) - } + require.NoErrorf(t, processDependencyImportValues(c, false), "processing import values dependencies") cc := common.Values(c.Values) for kk, vv := range e { pv, err := cc.PathValue(kk) - if err != nil { - t.Fatalf("retrieving import values table %v %v", kk, err) - } + require.NoError(t, err, "retrieving import values table %v", kk) switch pv := pv.(type) { case float64: - if s := strconv.FormatFloat(pv, 'f', -1, 64); s != vv { - t.Errorf("failed to match imported float value %v with expected %v for key %q", s, vv, kk) - } + s := strconv.FormatFloat(pv, 'f', -1, 64) + assert.Equalf(t, s, vv, "failed to match imported float value %v with expected %v for key %q", s, vv, kk) case bool: - if b := strconv.FormatBool(pv); b != vv { - t.Errorf("failed to match imported bool value %v with expected %v for key %q", b, vv, kk) - } + b := strconv.FormatBool(pv) + assert.Equalf(t, b, vv, "failed to match imported bool value %v with expected %v for key %q", b, vv, kk) default: - if pv != vv { - t.Errorf("failed to match imported string value %q with expected %q for key %q", pv, vv, kk) - } + assert.Equal(t, vv, pv, "failed to match imported string value %q with expected %q for key %q", pv, vv, kk) } } // Since this was processed with coalescing there should be no null values. // Here we verify that. _, err := cc.PathValue("ensurenull") - if err == nil { - t.Error("expect nil value not found but found it") - } + require.Error(t, err, "expect nil value not found but found it") var xerr common.ErrNoValue require.ErrorAs(t, err, &xerr, "expected an ErrNoValue") c = loadChart(t, "testdata/subpop") - if err := processDependencyImportValues(c, true); err != nil { - t.Fatalf("processing import values dependencies %v", err) - } + require.NoErrorf(t, processDependencyImportValues(c, true), "processing import values dependencies") cc = common.Values(c.Values) val, err := cc.PathValue("ensurenull") - if err != nil { - t.Error("expect value but ensurenull was not found") - } - if val != nil { - t.Errorf("expect nil value but got %q instead", val) - } + require.NoError(t, err, "expect value but ensurenull was not found") + assert.Nil(t, val, "expect nil value but got %q instead", val) } func TestProcessDependencyImportValuesFromSharedDependencyToAliases(t *testing.T) { c := loadChart(t, "testdata/chart-with-import-from-aliased-dependencies") - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - if err := processDependencyImportValues(c, true); err != nil { - t.Fatalf("processing import values dependencies %v", err) - } + require.NoErrorf(t, processDependencyEnabled(c, c.Values, ""), "expected no errors") + require.NoErrorf(t, processDependencyImportValues(c, true), "processing import values dependencies") e := make(map[string]string) e["foo-defaults.defaultValue"] = "42" @@ -293,12 +258,8 @@ func TestProcessDependencyImportValuesFromSharedDependencyToAliases(t *testing.T cValues := common.Values(c.Values) for kk, vv := range e { pv, err := cValues.PathValue(kk) - if err != nil { - t.Fatalf("retrieving import values table %v %v", kk, err) - } - if pv != vv { - t.Errorf("failed to match imported value %v with expected %v", pv, vv) - } + require.NoError(t, err, "retrieving import values table %v", kk) + assert.Equal(t, vv, pv, "failed to match imported value %v with expected %v", pv, vv) } } @@ -325,25 +286,18 @@ func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) { e["app2.service.port"] = "8080" e["app3.service.port"] = "9090" e["app4.service.port"] = "1234" - if err := processDependencyImportValues(c, true); err != nil { - t.Fatalf("processing import values dependencies %v", err) - } + require.NoErrorf(t, processDependencyImportValues(c, true), "processing import values dependencies") cc := common.Values(c.Values) for kk, vv := range e { pv, err := cc.PathValue(kk) - if err != nil { - t.Fatalf("retrieving import values table %v %v", kk, err) - } + require.NoError(t, err, "retrieving import values table %v", kk) switch pv := pv.(type) { case float64: - if s := strconv.FormatFloat(pv, 'f', -1, 64); s != vv { - t.Errorf("failed to match imported float value %v with expected %v", s, vv) - } + s := strconv.FormatFloat(pv, 'f', -1, 64) + assert.Equalf(t, s, vv, "failed to match imported float value %v with expected %v", s, vv) default: - if pv != vv { - t.Errorf("failed to match imported string value %q with expected %q", pv, vv) - } + assert.Equal(t, vv, pv, "failed to match imported string value %q with expected %q", pv, vv) } } } @@ -352,127 +306,71 @@ func TestProcessDependencyImportValuesForEnabledCharts(t *testing.T) { c := loadChart(t, "testdata/import-values-from-enabled-subchart/parent-chart") nameOverride := "parent-chart-prod" - if err := processDependencyImportValues(c, true); err != nil { - t.Fatalf("processing import values dependencies %v", err) - } - - if len(c.Dependencies()) != 2 { - t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - - if len(c.Dependencies()) != 1 { - t.Fatal("expected no changes in dependencies") - } - - if len(c.Metadata.Dependencies) != 1 { - t.Fatalf("expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies)) - } - + require.NoErrorf(t, processDependencyImportValues(c, true), "processing import values dependencies") + require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) + require.NoErrorf(t, processDependencyEnabled(c, c.Values, ""), "expected no errors") + require.Len(t, c.Dependencies(), 1, "expected no changes in dependencies") + require.Len(t, c.Metadata.Dependencies, 1, "expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies)) prodDependencyValues := c.Dependencies()[0].Values - if prodDependencyValues["nameOverride"] != nameOverride { - t.Fatalf("dependency chart name should be %s but got %s", nameOverride, prodDependencyValues["nameOverride"]) - } + require.Equal(t, nameOverride, prodDependencyValues["nameOverride"], "dependency chart name should be %s but got %s", nameOverride, prodDependencyValues["nameOverride"]) } func TestGetAliasDependency(t *testing.T) { c := loadChart(t, "testdata/frobnitz") req := c.Metadata.Dependencies - if len(req) == 0 { - t.Fatal("there are no dependencies to test") - } + require.NotEmpty(t, req, "there are no dependencies to test") // Success case aliasChart := getAliasDependency(c.Dependencies(), req[0]) - if aliasChart == nil { - t.Fatalf("failed to get dependency chart for alias %s", req[0].Name) - } + require.NotNil(t, aliasChart, "failed to get dependency chart for alias %s", req[0].Name) if req[0].Alias != "" { - if aliasChart.Name() != req[0].Alias { - t.Fatalf("dependency chart name should be %s but got %s", req[0].Alias, aliasChart.Name()) - } - } else if aliasChart.Name() != req[0].Name { - t.Fatalf("dependency chart name should be %s but got %s", req[0].Name, aliasChart.Name()) + require.Equal(t, req[0].Alias, aliasChart.Name(), "dependency chart name should be %s but got %s", req[0].Alias, aliasChart.Name()) + } else { + require.Equalf(t, aliasChart.Name(), req[0].Name, "dependency chart name should be %s but got %s", req[0].Name, aliasChart.Name()) } if req[0].Version != "" { - if !IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version) { - t.Fatal("dependency chart version is not in the compatible range") - } + require.True(t, IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version), "dependency chart version is not in the compatible range") } // Failure case req[0].Name = "something-else" - if aliasChart := getAliasDependency(c.Dependencies(), req[0]); aliasChart != nil { - t.Fatalf("expected no chart but got %s", aliasChart.Name()) - } + require.Nilf(t, getAliasDependency(c.Dependencies(), req[0]), "expected no chart") req[0].Version = "something else which is not in the compatible range" - if IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version) { - t.Fatal("dependency chart version outside the compatible range should fail, but it succeeded") - } + require.False(t, IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version), "dependency chart version outside the compatible range should fail, but it succeeded") } func TestDependentChartAliases(t *testing.T) { c := loadChart(t, "testdata/dependent-chart-alias") req := c.Metadata.Dependencies - if len(c.Dependencies()) != 2 { - t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - - if len(c.Dependencies()) != 3 { - t.Fatal("expected alias dependencies to be added") - } - - if len(c.Dependencies()) != len(c.Metadata.Dependencies) { - t.Fatalf("expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies())) - } + require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) + require.NoErrorf(t, processDependencyEnabled(c, c.Values, ""), "expected no errors") + require.Len(t, c.Dependencies(), 3, "expected alias dependencies to be added") + require.Len(t, c.Dependencies(), len(c.Metadata.Dependencies), "expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies())) aliasChart := getAliasDependency(c.Dependencies(), req[2]) - if aliasChart == nil { - t.Fatalf("failed to get dependency chart for alias %s", req[2].Name) - } - if aliasChart.Parent() != c { - t.Fatalf("dependency chart has wrong parent, expected %s but got %s", c.Name(), aliasChart.Parent().Name()) - } + require.NotNil(t, aliasChart, "failed to get dependency chart for alias %s", req[2].Name) + require.Equal(t, c, aliasChart.Parent(), "dependency chart has wrong parent, expected %s but got %s", c.Name(), aliasChart.Parent().Name()) if req[2].Alias != "" { - if aliasChart.Name() != req[2].Alias { - t.Fatalf("dependency chart name should be %s but got %s", req[2].Alias, aliasChart.Name()) - } - } else if aliasChart.Name() != req[2].Name { - t.Fatalf("dependency chart name should be %s but got %s", req[2].Name, aliasChart.Name()) + require.Equal(t, req[2].Alias, aliasChart.Name(), "dependency chart name should be %s but got %s", req[2].Alias, aliasChart.Name()) + } else { + require.Equalf(t, aliasChart.Name(), req[2].Name, "dependency chart name should be %s but got %s", req[2].Name, aliasChart.Name()) } req[2].Name = "dummy-name" - if aliasChart := getAliasDependency(c.Dependencies(), req[2]); aliasChart != nil { - t.Fatalf("expected no chart but got %s", aliasChart.Name()) - } + require.Nilf(t, getAliasDependency(c.Dependencies(), req[2]), "expected no chart") } func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) { c := loadChart(t, "testdata/dependent-chart-no-requirements-yaml") - if len(c.Dependencies()) != 2 { - t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - - if len(c.Dependencies()) != 2 { - t.Fatal("expected no changes in dependencies") - } + require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) + require.NoErrorf(t, processDependencyEnabled(c, c.Values, ""), "expected no errors") + require.Len(t, c.Dependencies(), 2, "expected no changes in dependencies") } func TestDependentChartWithSubChartsHelmignore(t *testing.T) { @@ -482,67 +380,37 @@ func TestDependentChartWithSubChartsHelmignore(t *testing.T) { func TestDependentChartsWithSubChartsSymlink(t *testing.T) { joonix := filepath.Join("testdata", "joonix") - if err := os.Symlink(filepath.Join("..", "..", "frobnitz"), filepath.Join(joonix, "charts", "frobnitz")); err != nil { - t.Fatal(err) - } + require.NoError(t, os.Symlink(filepath.Join("..", "..", "frobnitz"), filepath.Join(joonix, "charts", "frobnitz"))) defer os.RemoveAll(filepath.Join(joonix, "charts", "frobnitz")) c := loadChart(t, joonix) - if c.Name() != "joonix" { - t.Fatalf("unexpected chart name: %s", c.Name()) - } - if n := len(c.Dependencies()); n != 1 { - t.Fatalf("expected 1 dependency for this chart, but got %d", n) - } + require.Equal(t, "joonix", c.Name(), "unexpected chart name: %s", c.Name()) + require.Lenf(t, c.Dependencies(), 1, "expected 1 dependency for this chart") } func TestDependentChartsWithSubchartsAllSpecifiedInDependency(t *testing.T) { c := loadChart(t, "testdata/dependent-chart-with-all-in-requirements-yaml") - if len(c.Dependencies()) != 2 { - t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - - if len(c.Dependencies()) != 2 { - t.Fatal("expected no changes in dependencies") - } - - if len(c.Dependencies()) != len(c.Metadata.Dependencies) { - t.Fatalf("expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies())) - } + require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) + require.NoErrorf(t, processDependencyEnabled(c, c.Values, ""), "expected no errors") + require.Len(t, c.Dependencies(), 2, "expected no changes in dependencies") + require.Len(t, c.Dependencies(), len(c.Metadata.Dependencies), "expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies())) } func TestDependentChartsWithSomeSubchartsSpecifiedInDependency(t *testing.T) { c := loadChart(t, "testdata/dependent-chart-with-mixed-requirements-yaml") - if len(c.Dependencies()) != 2 { - t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } - - if len(c.Dependencies()) != 2 { - t.Fatal("expected no changes in dependencies") - } - - if len(c.Metadata.Dependencies) != 1 { - t.Fatalf("expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies)) - } + require.Len(t, c.Dependencies(), 2, "expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) + require.NoErrorf(t, processDependencyEnabled(c, c.Values, ""), "expected no errors") + require.Len(t, c.Dependencies(), 2, "expected no changes in dependencies") + require.Len(t, c.Metadata.Dependencies, 1, "expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies)) } func validateDependencyTree(t *testing.T, c *chart.Chart) { t.Helper() for _, dependency := range c.Dependencies() { if dependency.Parent() != c { - if dependency.Parent() != c { - t.Fatalf("dependency chart %s has wrong parent, expected %s but got %s", dependency.Name(), c.Name(), dependency.Parent().Name()) - } + require.Equal(t, c, dependency.Parent(), "dependency chart %s has wrong parent, expected %s but got %s", dependency.Name(), c.Name(), dependency.Parent().Name()) } // recurse entire tree validateDependencyTree(t, dependency) @@ -552,16 +420,9 @@ func validateDependencyTree(t *testing.T, c *chart.Chart) { func TestChartWithDependencyAliasedTwiceAndDoublyReferencedSubDependency(t *testing.T) { c := loadChart(t, "testdata/chart-with-dependency-aliased-twice") - if len(c.Dependencies()) != 1 { - t.Fatalf("expected one dependency for this chart, but got %d", len(c.Dependencies())) - } - - if err := processDependencyEnabled(c, c.Values, ""); err != nil { - t.Fatalf("expected no errors but got %q", err) - } + require.Len(t, c.Dependencies(), 1, "expected one dependency for this chart, but got %d", len(c.Dependencies())) + require.NoErrorf(t, processDependencyEnabled(c, c.Values, ""), "expected no errors") + require.Len(t, c.Dependencies(), 2, "expected two dependencies after processing aliases") - if len(c.Dependencies()) != 2 { - t.Fatal("expected two dependencies after processing aliases") - } validateDependencyTree(t, c) } diff --git a/pkg/registry/chart_test.go b/pkg/registry/chart_test.go index c80544b5b..95e429a0b 100644 --- a/pkg/registry/chart_test.go +++ b/pkg/registry/chart_test.go @@ -250,5 +250,5 @@ func TestGenerateOCICreatedAnnotations(t *testing.T) { require.NoError(t, err, "%s annotation with value '%s' not in RFC3339 format", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated]) // Verify creation annotation after (or equals) time test began - assert.False(t, nowTime.Before(createdTimeAnnotation), "%s annotation with value '%s' not configured properly. Annotation value is not after %s", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated], nowTimeString) + assert.GreaterOrEqual(t, nowTime, createdTimeAnnotation, "%s annotation with value '%s' not configured properly. Annotation value is not after %s", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated], nowTimeString) } diff --git a/pkg/repo/v1/chartrepo_test.go b/pkg/repo/v1/chartrepo_test.go index 9c992474b..4be2ebf73 100644 --- a/pkg/repo/v1/chartrepo_test.go +++ b/pkg/repo/v1/chartrepo_test.go @@ -18,7 +18,6 @@ package repo import ( "bytes" - "errors" "net/http" "net/http/httptest" "os" @@ -29,6 +28,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "sigs.k8s.io/yaml" "helm.sh/helm/v4/pkg/cli" @@ -68,37 +69,25 @@ func TestIndexCustomSchemeDownload(t *testing.T) { Name: repoName, URL: repoURL, }, providers) - if err != nil { - t.Fatalf("Problem loading chart repository from %s: %v", repoURL, err) - } + require.NoErrorf(t, err, "Problem loading chart repository from %s", repoURL) repo.CachePath = t.TempDir() tempIndexFile, err := os.CreateTemp(t.TempDir(), "test-repo") - if err != nil { - t.Fatalf("Failed to create temp index file: %v", err) - } + require.NoErrorf(t, err, "Failed to create temp index file") defer os.Remove(tempIndexFile.Name()) idx, err := repo.DownloadIndexFile() - if err != nil { - t.Fatalf("Failed to download index file to %s: %v", idx, err) - } + require.NoErrorf(t, err, "Failed to download index file to %s", idx) - if len(myCustomGetter.repoUrls) != 1 { - t.Fatal("Custom Getter.Get should be called once") - } + require.Len(t, myCustomGetter.repoUrls, 1, "Custom Getter.Get should be called once") expectedRepoIndexURL := repoURL + "/index.yaml" - if myCustomGetter.repoUrls[0] != expectedRepoIndexURL { - t.Fatalf("Custom Getter.Get should be called with %s", expectedRepoIndexURL) - } + require.Equalf(t, expectedRepoIndexURL, myCustomGetter.repoUrls[0], "Custom Getter.Get should be called with %s", expectedRepoIndexURL) } func TestConcurrencyDownloadIndex(t *testing.T) { srv, err := startLocalServerForTests(nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer srv.Close() repo, err := NewChartRepository(&Entry{ @@ -106,16 +95,12 @@ func TestConcurrencyDownloadIndex(t *testing.T) { URL: srv.URL, }, getter.All(&cli.EnvSettings{})) - if err != nil { - t.Fatalf("Problem loading chart repository from %s: %v", srv.URL, err) - } + require.NoErrorf(t, err, "Problem loading chart repository from %s", srv.URL) repo.CachePath = t.TempDir() // initial download index idx, err := repo.DownloadIndexFile() - if err != nil { - t.Fatalf("Failed to download index file to %s: %v", idx, err) - } + require.NoErrorf(t, err, "Failed to download index file to %s", idx) indexFName := filepath.Join(repo.CachePath, helmpath.CacheIndexFile(repo.Config.Name)) @@ -128,16 +113,12 @@ func TestConcurrencyDownloadIndex(t *testing.T) { for range 150 { wg.Go(func() { idx, err := repo.DownloadIndexFile() - if err != nil { - t.Errorf("Failed to download index file to %s: %v", idx, err) - } + assert.NoErrorf(t, err, "Failed to download index file to %s", idx) }) wg.Go(func() { _, err := LoadIndexFile(indexFName) - if err != nil { - t.Errorf("Failed to load index file: %v", err) - } + assert.NoErrorf(t, err, "Failed to load index file") }) } wg.Wait() @@ -175,9 +156,7 @@ func startLocalTLSServerForTests(handler http.Handler) (*httptest.Server, error) func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) { srv, err := startLocalTLSServerForTests(nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer srv.Close() chartURL, err := FindChartInRepoURL( @@ -186,12 +165,8 @@ func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) { getter.All(&cli.EnvSettings{}), WithInsecureSkipTLSVerify(true), ) - if err != nil { - t.Fatalf("%v", err) - } - if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" { - t.Errorf("%s is not the valid URL", chartURL) - } + require.NoError(t, err) + assert.Equalf(t, "https://charts.helm.sh/stable/nginx-0.2.0.tgz", chartURL, "%s is not the valid URL", chartURL) // If the insecureSkipTLSVerify is false, it will return an error that contains "x509: certificate signed by unknown authority". _, err = FindChartInRepoURL(srv.URL, "nginx", getter.All(&cli.EnvSettings{}), WithChartVersion("0.1.0")) @@ -203,33 +178,23 @@ func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) { if !strings.Contains(err.Error(), "x509: “Acme Co” certificate is not trusted") && !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { t.Errorf("Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err) } - } else if !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { - t.Errorf("Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err) + } else { + assert.ErrorContainsf(t, err, "x509: certificate signed by unknown authority", "Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error") } } func TestFindChartInRepoURL(t *testing.T) { srv, err := startLocalServerForTests(nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer srv.Close() chartURL, err := FindChartInRepoURL(srv.URL, "nginx", getter.All(&cli.EnvSettings{})) - if err != nil { - t.Fatalf("%v", err) - } - if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" { - t.Errorf("%s is not the valid URL", chartURL) - } + require.NoError(t, err) + assert.Equalf(t, "https://charts.helm.sh/stable/nginx-0.2.0.tgz", chartURL, "%s is not the valid URL", chartURL) chartURL, err = FindChartInRepoURL(srv.URL, "nginx", getter.All(&cli.EnvSettings{}), WithChartVersion("0.1.0")) - if err != nil { - t.Errorf("%s", err) - } - if chartURL != "https://charts.helm.sh/stable/nginx-0.1.0.tgz" { - t.Errorf("%s is not the valid URL", chartURL) - } + require.NoError(t, err) + assert.Equalf(t, "https://charts.helm.sh/stable/nginx-0.1.0.tgz", chartURL, "%s is not the valid URL", chartURL) } func TestErrorFindChartInRepoURL(t *testing.T) { @@ -237,38 +202,26 @@ func TestErrorFindChartInRepoURL(t *testing.T) { RepositoryCache: t.TempDir(), }) - if _, err := FindChartInRepoURL("http://someserver/something", "nginx", g); err == nil { - t.Error("Expected error for bad chart URL, but did not get any errors") - } else if !strings.Contains(err.Error(), `looks like "http://someserver/something" is not a valid chart repository or cannot be reached`) { - t.Errorf("Expected error for bad chart URL, but got a different error (%v)", err) - } + _, err := FindChartInRepoURL("http://someserver/something", "nginx", g) + require.Error(t, err, "Expected error for bad chart URL, but did not get any errors") + require.ErrorContainsf(t, err, `looks like "http://someserver/something" is not a valid chart repository or cannot be reached`, "Expected error for bad chart URL, but got a different error") srv, err := startLocalServerForTests(nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer srv.Close() - if _, err = FindChartInRepoURL(srv.URL, "nginx1", g); err == nil { - t.Error("Expected error for chart not found, but did not get any errors") - } else if err.Error() != `chart "nginx1" not found in `+srv.URL+` repository` { - t.Errorf("Expected error for chart not found, but got a different error (%v)", err) - } - if !errors.Is(err, ChartNotFoundError{}) { - t.Error("error is not of correct error type structure") - } + _, err = FindChartInRepoURL(srv.URL, "nginx1", g) + require.Error(t, err, "Expected error for chart not found, but did not get any errors") + require.EqualError(t, err, `chart "nginx1" not found in `+srv.URL+` repository`, "Expected error for chart not found, but got a different error") + require.ErrorIs(t, err, ChartNotFoundError{}, "error is not of correct error type structure") - if _, err = FindChartInRepoURL(srv.URL, "nginx1", g, WithChartVersion("0.1.0")); err == nil { - t.Error("Expected error for chart not found, but did not get any errors") - } else if err.Error() != `chart "nginx1" version "0.1.0" not found in `+srv.URL+` repository` { - t.Errorf("Expected error for chart not found, but got a different error (%v)", err) - } + _, err = FindChartInRepoURL(srv.URL, "nginx1", g, WithChartVersion("0.1.0")) + require.Error(t, err, "Expected error for chart not found, but did not get any errors") + require.EqualError(t, err, `chart "nginx1" version "0.1.0" not found in `+srv.URL+` repository`, "Expected error for chart not found, but got a different error") - if _, err = FindChartInRepoURL(srv.URL, "chartWithNoURL", g); err == nil { - t.Error("Expected error for no chart URLs available, but did not get any errors") - } else if err.Error() != `chart "chartWithNoURL" has no downloadable URLs` { - t.Errorf("Expected error for chart not found, but got a different error (%v)", err) - } + _, err = FindChartInRepoURL(srv.URL, "chartWithNoURL", g) + require.Error(t, err, "Expected error for no chart URLs available, but did not get any errors") + assert.EqualError(t, err, `chart "chartWithNoURL" has no downloadable URLs`, "Expected error for chart not found, but got a different error") } func TestResolveReferenceURL(t *testing.T) { @@ -286,11 +239,7 @@ func TestResolveReferenceURL(t *testing.T) { {"http://localhost:8123/charts?with=queryparameter", "/nginx-0.2.0.tgz", "http://localhost:8123/nginx-0.2.0.tgz?with=queryparameter"}, } { chartURL, err := ResolveReferenceURL(tt.baseURL, tt.refURL) - if err != nil { - t.Errorf("unexpected error in ResolveReferenceURL(%q, %q): %s", tt.baseURL, tt.refURL, err) - } - if chartURL != tt.chartURL { - t.Errorf("expected ResolveReferenceURL(%q, %q) to equal %q, got %q", tt.baseURL, tt.refURL, tt.chartURL, chartURL) - } + require.NoErrorf(t, err, "unexpected error in ResolveReferenceURL(%q, %q)", tt.baseURL, tt.refURL) + assert.Equalf(t, chartURL, tt.chartURL, "expected ResolveReferenceURL(%q, %q) to equal %q, got %q", tt.baseURL, tt.refURL, tt.chartURL, chartURL) } } From d4ec8e9b87a002d48c2d7a8f3c145c9a324bfaef Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:55 +0200 Subject: [PATCH 461/571] chore(pkg): refactor: convert tests to testify assert/require part 1 refactor: convert tests to testify assert/require in pkg/engine Signed-off-by: Matthieu MOREL --- pkg/engine/engine_test.go | 395 +++++++++----------------------------- 1 file changed, 96 insertions(+), 299 deletions(-) diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index f666b63a6..ecc1eee3b 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -51,9 +51,7 @@ func TestSortTemplates(t *testing.T) { "/mychart/templates/charts/bar/templates/foo.tpl": {}, } got := sortTemplates(tpls) - if len(got) != len(tpls) { - t.Fatal("Sorted results are missing templates") - } + require.Len(t, got, len(tpls), "Sorted results are missing templates") expect := []string{ "/mychart/templates/charts/foo/charts/bar/templates/foo.tpl", @@ -65,12 +63,7 @@ func TestSortTemplates(t *testing.T) { "/mychart/templates/_foo.tpl", } for i, e := range expect { - if got[i] != e { - t.Fatalf("\n\tExp:\n%s\n\tGot:\n%s", - strings.Join(expect, "\n"), - strings.Join(got, "\n"), - ) - } + require.Equal(t, e, got[i], "\n\tExp:\n%s\n\tGot:\n%s", strings.Join(expect, "\n"), strings.Join(got, "\n")) } } @@ -78,17 +71,15 @@ func TestFuncMap(t *testing.T) { fns := funcMap() forbidden := []string{"env", "expandenv"} for _, f := range forbidden { - if _, ok := fns[f]; ok { - t.Errorf("Forbidden function %s exists in FuncMap.", f) - } + _, ok := fns[f] + assert.Falsef(t, ok, "Forbidden function %s exists in FuncMap.", f) } // Test for Engine-specific template functions. expect := []string{"include", "required", "tpl", "toYaml", "fromYaml", "toToml", "fromToml", "toJson", "fromJson", "lookup"} for _, f := range expect { - if _, ok := fns[f]; !ok { - t.Errorf("Expected add-on function %q", f) - } + _, ok := fns[f] + assert.Truef(t, ok, "Expected add-on function %q", f) } } @@ -120,13 +111,9 @@ func TestRender(t *testing.T) { } v, err := util.CoalesceValues(c, vals) - if err != nil { - t.Fatalf("Failed to coalesce values: %s", err) - } + require.NoError(t, err, "Failed to coalesce values") out, err := Render(c, v) - if err != nil { - t.Errorf("Failed to render templates: %s", err) - } + require.NoError(t, err, "Failed to render templates") expect := map[string]string{ "moby/templates/test1": "Spouter Inn", @@ -137,9 +124,7 @@ func TestRender(t *testing.T) { } for name, data := range expect { - if out[name] != data { - t.Errorf("Expected %q, got %q", data, out[name]) - } + assert.Equal(t, data, out[name], "Expected %q, got %q", data, out[name]) } } @@ -173,14 +158,10 @@ func TestRenderRefsOrdering(t *testing.T) { for i := range 100 { out, err := Render(parentChart, common.Values{}) - if err != nil { - t.Fatalf("Failed to render templates: %s", err) - } + require.NoError(t, err, "Failed to render templates") for name, data := range expect { - if out[name] != data { - t.Fatalf("Expected %q, got %q (iteration %d)", data, out[name], i+1) - } + require.Equal(t, data, out[name], "Expected %q, got %q (iteration %d)", data, out[name], i+1) } } } @@ -198,25 +179,12 @@ func TestRenderInternals(t *testing.T) { } out, err := new(Engine).render(t.Context(), tpls) - if err != nil { - t.Fatalf("Failed template rendering: %s", err) - } - - if len(out) != 3 { - t.Fatalf("Expected 3 templates, got %d", len(out)) - } - - if out["one"] != "Hello One" { - t.Errorf("Expected 'Hello One', got %q", out["one"]) - } - if out["two"] != "Goodbye TWO" { - t.Errorf("Expected 'Goodbye TWO'. got %q", out["two"]) - } - - if out["three"] != "Goodbye THREE" { - t.Errorf("Expected 'Goodbye THREE'. got %q", out["two"]) - } + require.NoError(t, err, "Failed template rendering") + require.Len(t, out, 3, "Expected 3 templates, got %d", len(out)) + assert.Equal(t, "Hello One", out["one"]) + assert.Equal(t, "Goodbye TWO", out["two"]) + assert.Equal(t, "Goodbye THREE", out["three"]) } func TestRenderWithDNS(t *testing.T) { @@ -236,22 +204,16 @@ func TestRenderWithDNS(t *testing.T) { } v, err := util.CoalesceValues(c, vals) - if err != nil { - t.Fatalf("Failed to coalesce values: %s", err) - } + require.NoError(t, err, "Failed to coalesce values") var e Engine e.EnableDNS = true out, err := e.Render(c, v) - if err != nil { - t.Errorf("Failed to render templates: %s", err) - } + require.NoError(t, err, "Failed to render templates") for _, val := range c.Templates { fp := path.Join("moby", val.Name) - if out[fp] == "" { - t.Errorf("Expected IP address, got %q", out[fp]) - } + assert.NotEmpty(t, out[fp], "Expected IP address, got %q", out[fp]) } } @@ -375,21 +337,15 @@ func TestRenderWithClientProvider(t *testing.T) { } v, err := util.CoalesceValues(c, vals) - if err != nil { - t.Fatalf("Failed to coalesce values: %s", err) - } + require.NoError(t, err, "Failed to coalesce values") out, err := RenderWithClientProvider(c, v, provider) - if err != nil { - t.Errorf("Failed to render templates: %s", err) - } + require.NoError(t, err, "Failed to render templates") for name, want := range cases { t.Run(name, func(t *testing.T) { key := path.Join("moby/templates", name) - if out[key] != want.output { - t.Errorf("Expected %q, got %q", want, out[key]) - } + assert.Equal(t, want.output, out[key], "Expected %q, got %q", want, out[key]) }) } } @@ -411,9 +367,7 @@ func TestRenderWithClientProvider_error(t *testing.T) { } v, err := util.CoalesceValues(c, vals) - if err != nil { - t.Fatalf("Failed to coalesce values: %s", err) - } + require.NoError(t, err, "Failed to coalesce values") provider := &testClientProvider{ t: t, @@ -424,9 +378,7 @@ func TestRenderWithClientProvider_error(t *testing.T) { }, } _, err = RenderWithClientProvider(c, v, provider) - if err == nil || !strings.Contains(err.Error(), "kaboom") { - t.Errorf("Expected error from client provider when rendering, got %q", err) - } + assert.ErrorContainsf(t, err, "kaboom", "Expected error from client provider when rendering") } func TestParallelRenderInternals(t *testing.T) { @@ -444,12 +396,8 @@ func TestParallelRenderInternals(t *testing.T) { }, } out, err := e.render(t.Context(), tpls) - if err != nil { - t.Errorf("Failed to render %s: %s", tt, err) - } - if out["t"] != tt { - t.Errorf("Expected %q, got %q", tt, out["t"]) - } + assert.NoError(t, err, "Failed to render %s", tt) + assert.Equal(t, tt, out["t"], "Expected %q, got %q", tt, out["t"]) wg.Done() }(i) } @@ -463,13 +411,9 @@ func TestParseErrors(t *testing.T) { "undefined_function": {tpl: `{{foo}}`, vals: vals}, } _, err := new(Engine).render(t.Context(), tplsUndefinedFunction) - if err == nil { - t.Fatalf("Expected failures while rendering: %s", err) - } + require.Error(t, err, "Expected failures while rendering") expected := `parse error at (undefined_function:1): function "foo" not defined` - if err.Error() != expected { - t.Errorf("Expected '%s', got %q", expected, err.Error()) - } + assert.EqualError(t, err, expected) } func TestExecErrors(t *testing.T) { @@ -526,12 +470,8 @@ linebreak`, for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { _, err := new(Engine).render(t.Context(), tt.tpls) - if err == nil { - t.Fatalf("Expected failures while rendering: %s", err) - } - if err.Error() != tt.expected { - t.Errorf("Expected %q, got %q", tt.expected, err.Error()) - } + require.Error(t, err, "Expected failures while rendering") + assert.EqualError(t, err, tt.expected) }) } } @@ -544,25 +484,15 @@ func TestFailErrors(t *testing.T) { "failtpl": {tpl: failtpl, vals: vals}, } _, err := new(Engine).render(t.Context(), tplsFailed) - if err == nil { - t.Fatalf("Expected failures while rendering: %s", err) - } + require.Error(t, err, "Expected failures while rendering") expected := `execution error at (failtpl:1:33): This is an error` - if err.Error() != expected { - t.Errorf("Expected '%s', got %q", expected, err.Error()) - } + require.EqualError(t, err, expected) var e Engine e.LintMode = true out, err := e.render(t.Context(), tplsFailed) - if err != nil { - t.Fatal(err) - } - - expectStr := "All your base are belong to us" - if gotStr := out["failtpl"]; gotStr != expectStr { - t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out) - } + require.NoError(t, err) + assert.Equal(t, "All your base are belong to us", out["failtpl"]) } func TestAllTemplates(t *testing.T) { @@ -592,9 +522,7 @@ func TestAllTemplates(t *testing.T) { dep1.AddDependency(dep2) tpls := allTemplates(ch1, common.Values{}) - if len(tpls) != 5 { - t.Errorf("Expected 5 charts, got %d", len(tpls)) - } + assert.Len(t, tpls, 5, "Expected 5 charts, got %d", len(tpls)) } func TestChartValuesContainsIsRoot(t *testing.T) { @@ -614,17 +542,13 @@ func TestChartValuesContainsIsRoot(t *testing.T) { ch1.AddDependency(dep1) out, err := Render(ch1, common.Values{}) - if err != nil { - t.Fatalf("failed to render templates: %s", err) - } + require.NoError(t, err, "failed to render templates") expects := map[string]string{ "parent/charts/child/templates/isroot": "false", "parent/templates/isroot": "true", } for file, expect := range expects { - if out[file] != expect { - t.Errorf("Expected %q, got %q", expect, out[file]) - } + assert.Equal(t, expect, out[file], "Expected %q, got %q", expect, out[file]) } } @@ -646,18 +570,10 @@ func TestRenderDependency(t *testing.T) { }) out, err := Render(ch, map[string]any{}) - if err != nil { - t.Fatalf("failed to render chart: %s", err) - } - - if len(out) != 2 { - t.Errorf("Expected 2, got %d", len(out)) - } + require.NoError(t, err, "failed to render chart") - expect := "Hello World" - if out["outerchart/templates/outer"] != expect { - t.Errorf("Expected %q, got %q", expect, out["outer"]) - } + assert.Len(t, out, 2, "Expected 2, got %d", len(out)) + assert.Equal(t, "Hello World", out["outerchart/templates/outer"]) } func TestRenderNestedValues(t *testing.T) { @@ -719,9 +635,7 @@ func TestRenderNestedValues(t *testing.T) { } tmp, err := util.CoalesceValues(outer, injValues) - if err != nil { - t.Fatalf("Failed to coalesce values: %s", err) - } + require.NoError(t, err, "Failed to coalesce values") inject := common.Values{ "Values": tmp, @@ -734,34 +648,22 @@ func TestRenderNestedValues(t *testing.T) { t.Logf("Calculated values: %v", inject) out, err := Render(outer, inject) - if err != nil { - t.Fatalf("failed to render templates: %s", err) - } + require.NoError(t, err, "failed to render templates") fullouterpath := "top/" + outerpath - if out[fullouterpath] != "Gather ye rosebuds while ye may" { - t.Errorf("Unexpected outer: %q", out[fullouterpath]) - } + assert.Equal(t, "Gather ye rosebuds while ye may", out[fullouterpath], "Unexpected outer: %q", out[fullouterpath]) fullinnerpath := "top/charts/herrick/" + innerpath - if out[fullinnerpath] != "Old time is still a-flyin'" { - t.Errorf("Unexpected inner: %q", out[fullinnerpath]) - } + assert.Equal(t, "Old time is still a-flyin'", out[fullinnerpath], "Unexpected inner: %q", out[fullinnerpath]) fulldeepestpath := "top/charts/herrick/charts/deepest/" + deepestpath - if out[fulldeepestpath] != "And this same flower that smiles to-day" { - t.Errorf("Unexpected deepest: %q", out[fulldeepestpath]) - } + assert.Equal(t, "And this same flower that smiles to-day", out[fulldeepestpath], "Unexpected deepest: %q", out[fulldeepestpath]) fullcheckrelease := "top/charts/herrick/charts/deepest/" + checkrelease - if out[fullcheckrelease] != "Tomorrow will be dyin" { - t.Errorf("Unexpected release: %q", out[fullcheckrelease]) - } + assert.Equal(t, "Tomorrow will be dyin", out[fullcheckrelease], "Unexpected release: %q", out[fullcheckrelease]) fullchecksubcharts := "top/" + subchartspath - if out[fullchecksubcharts] != "The glorious Lamp of Heaven, the Sun" { - t.Errorf("Unexpected subcharts: %q", out[fullchecksubcharts]) - } + assert.Equal(t, "The glorious Lamp of Heaven, the Sun", out[fullchecksubcharts], "Unexpected subcharts: %q", out[fullchecksubcharts]) } func TestRenderBuiltinValues(t *testing.T) { @@ -798,9 +700,7 @@ func TestRenderBuiltinValues(t *testing.T) { t.Logf("Calculated values: %v", outer) out, err := Render(outer, inject) - if err != nil { - t.Fatalf("failed to render templates: %s", err) - } + require.NoError(t, err, "failed to render templates") expects := map[string]string{ "Troy/charts/Latium/templates/Lavinia": "Troy/charts/Latium/templates/LaviniaLatiumAeneid", @@ -809,9 +709,7 @@ func TestRenderBuiltinValues(t *testing.T) { "Troy/charts/Latium/templates/From": "Virgil Aeneid", } for file, expect := range expects { - if out[file] != expect { - t.Errorf("Expected %q, got %q", expect, out[file]) - } + assert.Equal(t, expect, out[file], "Expected %q, got %q", expect, out[file]) } } @@ -843,20 +741,12 @@ func TestAlterFuncMap_include(t *testing.T) { } out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } - - expect := " Mistah Kurtz - he dead." - if got := out["conrad/templates/quote"]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, out) - } + require.NoError(t, err) + assert.Equal(t, " Mistah Kurtz - he dead.", out["conrad/templates/quote"]) _, err = Render(d, v) expectErrName := "nested/templates/quote" - if err == nil { - t.Errorf("Expected err of nested reference name: %v", expectErrName) - } + assert.Error(t, err, "Expected err of nested reference name: %v", expectErrName) } func TestAlterFuncMap_require(t *testing.T) { @@ -881,18 +771,10 @@ func TestAlterFuncMap_require(t *testing.T) { } out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - expectStr := "All your base are belong to us" - if gotStr := out["conan/templates/quote"]; gotStr != expectStr { - t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out) - } - expectNum := "All 2 of them!" - if gotNum := out["conan/templates/bases"]; gotNum != expectNum { - t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, out) - } + assert.Equal(t, "All your base are belong to us", out["conan/templates/quote"]) + assert.Equal(t, "All 2 of them!", out["conan/templates/bases"]) // test required without passing in needed values with lint mode on // verifies lint replaces required with an empty string (should not fail) @@ -907,19 +789,11 @@ func TestAlterFuncMap_require(t *testing.T) { } var e Engine e.LintMode = true - out, err = e.Render(c, lintValues) - if err != nil { - t.Fatal(err) - } - expectStr = "All your base are belong to us" - if gotStr := out["conan/templates/quote"]; gotStr != expectStr { - t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out) - } - expectNum = "All of them!" - if gotNum := out["conan/templates/bases"]; gotNum != expectNum { - t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, out) - } + out, err = e.Render(c, lintValues) + require.NoError(t, err) + assert.Equal(t, "All your base are belong to us", out["conan/templates/quote"]) + assert.Equal(t, "All of them!", out["conan/templates/bases"]) } func TestAlterFuncMap_tpl(t *testing.T) { @@ -941,14 +815,8 @@ func TestAlterFuncMap_tpl(t *testing.T) { } out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } - - expect := "Evaluate tpl Value: myvalue" - if got := out["TplFunction/templates/base"]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, out) - } + require.NoError(t, err) + assert.Equal(t, "Evaluate tpl Value: myvalue", out["TplFunction/templates/base"]) } func TestAlterFuncMap_tplfunc(t *testing.T) { @@ -970,14 +838,8 @@ func TestAlterFuncMap_tplfunc(t *testing.T) { } out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } - - expect := "Evaluate tpl Value: \"myvalue\"" - if got := out["TplFunction/templates/base"]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, out) - } + require.NoError(t, err) + assert.Equal(t, "Evaluate tpl Value: \"myvalue\"", out["TplFunction/templates/base"]) } func TestAlterFuncMap_tplinclude(t *testing.T) { @@ -1000,14 +862,8 @@ func TestAlterFuncMap_tplinclude(t *testing.T) { } out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } - - expect := "\"TplFunction/templates/base\"" - if got := out["TplFunction/templates/base"]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, out) - } + require.NoError(t, err) + assert.Equal(t, "\"TplFunction/templates/base\"", out["TplFunction/templates/base"]) } func TestRenderRecursionLimit(t *testing.T) { @@ -1053,9 +909,7 @@ func TestRenderRecursionLimit(t *testing.T) { } out, err := Render(d, v) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) var expect string var expectSb1062 strings.Builder @@ -1063,9 +917,7 @@ func TestRenderRecursionLimit(t *testing.T) { expectSb1062.WriteString(phrase + "\n") } expect += expectSb1062.String() - if got := out["overlook/templates/quote"]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, out) - } + assert.Equal(t, expect, out["overlook/templates/quote"]) } func TestRenderLoadTemplateForTplFromFile(t *testing.T) { @@ -1094,14 +946,8 @@ func TestRenderLoadTemplateForTplFromFile(t *testing.T) { } out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } - - expect := "test-function nested-define-content" - if got := out["TplLoadFromFile/templates/base"]; got != expect { - t.Fatalf("Expected %q, got %q", expect, got) - } + require.NoError(t, err) + require.Equal(t, "test-function nested-define-content", out["TplLoadFromFile/templates/base"]) } func TestRenderTplEmpty(t *testing.T) { @@ -1122,9 +968,7 @@ func TestRenderTplEmpty(t *testing.T) { } out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) expects := map[string]string{ "TplEmpty/templates/empty-string": "", @@ -1132,9 +976,7 @@ func TestRenderTplEmpty(t *testing.T) { "TplEmpty/templates/only-defines": "", } for file, expect := range expects { - if out[file] != expect { - t.Errorf("Expected %q, got %q", expect, out[file]) - } + assert.Equal(t, expect, out[file], "Expected %q, got %q", expect, out[file]) } } @@ -1168,9 +1010,7 @@ func TestRenderTplTemplateNames(t *testing.T) { } out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) expects := map[string]string{ "TplTemplateNames/templates/default-basepath": "TplTemplateNames/templates", @@ -1180,9 +1020,7 @@ func TestRenderTplTemplateNames(t *testing.T) { "TplTemplateNames/templates/modified-field": "extra-field", } for file, expect := range expects { - if out[file] != expect { - t.Errorf("Expected %q, got %q", expect, out[file]) - } + assert.Equal(t, expect, out[file], "Expected %q, got %q", expect, out[file]) } } @@ -1232,9 +1070,7 @@ func TestRenderTplRedefines(t *testing.T) { } out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) expects := map[string]string{ "TplRedefines/templates/partial": `before: original-in-partial\ntpl: redefined-in-tpl\nafter: original-in-partial`, @@ -1247,9 +1083,7 @@ func TestRenderTplRedefines(t *testing.T) { `after: original-in-manifest original-outer-in-manifest`, } for file, expect := range expects { - if out[file] != expect { - t.Errorf("Expected %q, got %q", expect, out[file]) - } + assert.Equal(t, expect, out[file], "Expected %q, got %q", expect, out[file]) } } @@ -1272,17 +1106,13 @@ func TestRenderTplMissingKey(t *testing.T) { } out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) expects := map[string]string{ "TplMissingKey/templates/manifest": `missingValue: `, } for file, expect := range expects { - if out[file] != expect { - t.Errorf("Expected %q, got %q", expect, out[file]) - } + assert.Equal(t, expect, out[file], "Expected %q, got %q", expect, out[file]) } } @@ -1307,15 +1137,9 @@ func TestRenderTplMissingKeyString(t *testing.T) { e := new(Engine) e.Strict = true - out, err := e.Render(c, v) - if err == nil { - t.Errorf("Expected error, got %v", out) - return - } - errTxt := fmt.Sprint(err) - if !strings.Contains(errTxt, "noSuchKey") { - t.Errorf("Expected error to contain 'noSuchKey', got %s", errTxt) - } + _, err := e.Render(c, v) + require.Error(t, err) + assert.ErrorContains(t, err, "noSuchKey") } func TestNestedHelpersProducesMultilineStacktrace(t *testing.T) { @@ -1354,7 +1178,7 @@ NestedHelperFunctions/charts/common/templates/_helpers_2.tpl:1:49 _, err := Render(c, vals) require.Error(t, err) - assert.Equal(t, expectedErrorMessage, err.Error()) + assert.EqualError(t, err, expectedErrorMessage) } func TestMultilineNoTemplateAssociatedError(t *testing.T) { @@ -1374,11 +1198,6 @@ func TestMultilineNoTemplateAssociatedError(t *testing.T) { }, } - expectedErrorMessage := `multiline/templates/svc.yaml:1:9 - executing "multiline/templates/svc.yaml" at : - error calling include: -template: no template "nested_helper.name" associated with template "gotpl"` - v := common.Values{} val, _ := util.CoalesceValues(c, v) @@ -1388,7 +1207,10 @@ template: no template "nested_helper.name" associated with template "gotpl"` _, err := Render(c, vals) require.Error(t, err) - assert.Equal(t, expectedErrorMessage, err.Error()) + assert.EqualError(t, err, `multiline/templates/svc.yaml:1:9 + executing "multiline/templates/svc.yaml" at : + error calling include: +template: no template "nested_helper.name" associated with template "gotpl"`) } func TestRenderCustomTemplateFuncs(t *testing.T) { @@ -1436,9 +1258,7 @@ func TestRenderCustomTemplateFuncs(t *testing.T) { // Render the chart. out, err := e.Render(c, v) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Expected output should be "hello!!!". expected := "hello!!!" @@ -1461,12 +1281,8 @@ func TestTraceableError_SimpleForm(t *testing.T) { } for _, errString := range testStrings { trace, done := parseTemplateSimpleErrorString(errString) - if !done { - t.Error("Expected parse to pass but did not") - } - if trace.message != "error calling include" { - t.Errorf("Expected %q, got %q", errString, trace.message) - } + assert.True(t, done, "Expected parse to pass but did not") + assert.Equal(t, "error calling include", trace.message, "Expected %q, got %q", errString, trace.message) } } func TestTraceableError_ExecutingForm(t *testing.T) { @@ -1478,12 +1294,8 @@ func TestTraceableError_ExecutingForm(t *testing.T) { errString := errTuple[0] expectedLocation := errTuple[1] trace, done := parseTemplateExecutingAtErrorType(errString) - if !done { - t.Error("Expected parse to pass but did not") - } - if trace.location != expectedLocation { - t.Errorf("Expected %q, got %q", expectedLocation, trace.location) - } + assert.True(t, done, "Expected parse to pass but did not") + assert.Equal(t, expectedLocation, trace.location, "Expected %q, got %q", expectedLocation, trace.location) } } @@ -1493,12 +1305,8 @@ func TestTraceableError_NoTemplateForm(t *testing.T) { } for _, errString := range testStrings { trace, done := parseTemplateNoTemplateError(errString, errString) - if !done { - t.Error("Expected parse to pass but did not") - } - if trace.message != errString { - t.Errorf("Expected %q, got %q", errString, trace.message) - } + assert.True(t, done, "Expected parse to pass but did not") + assert.Equal(t, errString, trace.message, "Expected %q, got %q", errString, trace.message) } } @@ -1535,9 +1343,7 @@ func TestRenderSubchartDefaultNilNoStringify(t *testing.T) { injValues := map[string]any{} tmp, err := util.CoalesceValues(parent, injValues) - if err != nil { - t.Fatalf("Failed to coalesce values: %s", err) - } + require.NoError(t, err, "Failed to coalesce values") inject := common.Values{ "Values": tmp, @@ -1548,18 +1354,9 @@ func TestRenderSubchartDefaultNilNoStringify(t *testing.T) { } out, err := Render(parent, inject) - if err != nil { - t.Fatalf("Failed to render templates: %s", err) - } + require.NoError(t, err, "Failed to render templates") rendered := out["parent/charts/child/templates/test.yaml"] - - if strings.Contains(rendered, "%!s()") { - t.Errorf("Rendered output contains %%!s(), got: %q", rendered) - } - - expected := "subPath: fallback" - if rendered != expected { - t.Errorf("Expected %q, got %q", expected, rendered) - } + assert.NotContains(t, rendered, "%!s()", "Rendered output contains %%!s(), got: %q", rendered) + assert.Equal(t, "subPath: fallback", rendered) } From 0daa77fa9bffe2c8359bb079d3e978fe74a0b5a4 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:57 +0200 Subject: [PATCH 462/571] chore(pkg): refactor: convert tests to testify assert/require part 2 refactor: convert tests to testify assert/require in pkg Signed-off-by: Matthieu MOREL --- pkg/cmd/flags_test.go | 9 +- pkg/getter/httpgetter_test.go | 345 +++++++++------------------------- 2 files changed, 93 insertions(+), 261 deletions(-) diff --git a/pkg/cmd/flags_test.go b/pkg/cmd/flags_test.go index 614970252..09dc61638 100644 --- a/pkg/cmd/flags_test.go +++ b/pkg/cmd/flags_test.go @@ -110,14 +110,11 @@ func TestPostRendererFlagSetOnce(t *testing.T) { }, } // Set the plugin name once - err := str.Set("postrenderer-v1") - require.NoError(t, err) + require.NoError(t, str.Set("postrenderer-v1")) // Set the plugin name again to the same value is not ok - err = str.Set("postrenderer-v1") - require.Error(t, err) + require.Error(t, str.Set("postrenderer-v1")) // Set the plugin name again to a different value is not ok - err = str.Set("cat") - require.Error(t, err) + require.Error(t, str.Set("cat")) } diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index d7ba770b3..bfde5efa8 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -28,6 +28,9 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/internal/tlsutil" "helm.sh/helm/v4/internal/version" "helm.sh/helm/v4/pkg/cli" @@ -35,13 +38,10 @@ import ( func TestHTTPGetter(t *testing.T) { g, err := NewHTTPGetter(WithURL("http://example.com")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if _, ok := g.(*HTTPGetter); !ok { - t.Fatal("Expected NewHTTPGetter to produce an *HTTPGetter") - } + _, ok := g.(*HTTPGetter) + require.True(t, ok, "Expected NewHTTPGetter to produce an *HTTPGetter") cd := "../../testdata" join := filepath.Join @@ -60,54 +60,20 @@ func TestHTTPGetter(t *testing.T) { WithTimeout(timeout), WithTransport(transport), ) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) hg, ok := g.(*HTTPGetter) - if !ok { - t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter") - } - - if hg.opts.username != "I" { - t.Errorf("Expected NewHTTPGetter to contain %q as the username, got %q", "I", hg.opts.username) - } - - if hg.opts.password != "Am" { - t.Errorf("Expected NewHTTPGetter to contain %q as the password, got %q", "Am", hg.opts.password) - } - - if hg.opts.passCredentialsAll != false { - t.Errorf("Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", false, hg.opts.passCredentialsAll) - } - - if hg.opts.userAgent != "Groot" { - t.Errorf("Expected NewHTTPGetter to contain %q as the user agent, got %q", "Groot", hg.opts.userAgent) - } - - if hg.opts.certFile != pub { - t.Errorf("Expected NewHTTPGetter to contain %q as the public key file, got %q", pub, hg.opts.certFile) - } - - if hg.opts.keyFile != priv { - t.Errorf("Expected NewHTTPGetter to contain %q as the private key file, got %q", priv, hg.opts.keyFile) - } - - if hg.opts.caFile != ca { - t.Errorf("Expected NewHTTPGetter to contain %q as the CA file, got %q", ca, hg.opts.caFile) - } - - if hg.opts.insecureSkipVerifyTLS != insecure { - t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", false, hg.opts.insecureSkipVerifyTLS) - } - - if hg.opts.timeout != timeout { - t.Errorf("Expected NewHTTPGetter to contain %s as Timeout flag, got %s", timeout, hg.opts.timeout) - } - - if hg.opts.transport != transport { - t.Errorf("Expected NewHTTPGetter to contain %p as Transport, got %p", transport, hg.opts.transport) - } + require.True(t, ok, "expected NewHTTPGetter to produce an *HTTPGetter") + assert.Equal(t, "I", hg.opts.username, "Expected NewHTTPGetter to contain %q as the username, got %q", "I", hg.opts.username) + assert.Equal(t, "Am", hg.opts.password, "Expected NewHTTPGetter to contain %q as the password, got %q", "Am", hg.opts.password) + assert.False(t, hg.opts.passCredentialsAll, "Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", false, hg.opts.passCredentialsAll) + assert.Equal(t, "Groot", hg.opts.userAgent, "Expected NewHTTPGetter to contain %q as the user agent, got %q", "Groot", hg.opts.userAgent) + assert.Equal(t, pub, hg.opts.certFile, "Expected NewHTTPGetter to contain %q as the public key file, got %q", pub, hg.opts.certFile) + assert.Equal(t, priv, hg.opts.keyFile, "Expected NewHTTPGetter to contain %q as the private key file, got %q", priv, hg.opts.keyFile) + assert.Equal(t, ca, hg.opts.caFile, "Expected NewHTTPGetter to contain %q as the CA file, got %q", ca, hg.opts.caFile) + assert.Equal(t, insecure, hg.opts.insecureSkipVerifyTLS, "Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", false, hg.opts.insecureSkipVerifyTLS) + assert.Equal(t, timeout, hg.opts.timeout, "Expected NewHTTPGetter to contain %s as Timeout flag, got %s", timeout, hg.opts.timeout) + assert.Same(t, transport, hg.opts.transport, "Expected NewHTTPGetter to contain %p as Transport, got %p", transport, hg.opts.transport) // Test if setting insecureSkipVerifyTLS is being passed to the ops insecure = true @@ -115,65 +81,42 @@ func TestHTTPGetter(t *testing.T) { g, err = NewHTTPGetter( WithInsecureSkipVerifyTLS(insecure), ) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) hg, ok = g.(*HTTPGetter) - if !ok { - t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter") - } - - if hg.opts.insecureSkipVerifyTLS != insecure { - t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", insecure, hg.opts.insecureSkipVerifyTLS) - } + require.True(t, ok, "expected NewHTTPGetter to produce an *HTTPGetter") + assert.Equal(t, insecure, hg.opts.insecureSkipVerifyTLS, "Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", insecure, hg.opts.insecureSkipVerifyTLS) // Checking false by default - if hg.opts.passCredentialsAll != false { - t.Errorf("Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", false, hg.opts.passCredentialsAll) - } + assert.False(t, hg.opts.passCredentialsAll, "Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", false, hg.opts.passCredentialsAll) // Test setting PassCredentialsAll g, err = NewHTTPGetter( WithBasicAuth("I", "Am"), WithPassCredentialsAll(true), ) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) hg, ok = g.(*HTTPGetter) - if !ok { - t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter") - } - if hg.opts.passCredentialsAll != true { - t.Errorf("Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", true, hg.opts.passCredentialsAll) - } + require.True(t, ok, "expected NewHTTPGetter to produce an *HTTPGetter") + assert.True(t, hg.opts.passCredentialsAll, "Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", true, hg.opts.passCredentialsAll) } func TestDownload(t *testing.T) { expect := "Call me Ishmael" srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defaultUserAgent := version.GetUserAgent() - if r.UserAgent() != defaultUserAgent { - t.Errorf("Expected '%s', got '%s'", defaultUserAgent, r.UserAgent()) - } + assert.Equal(t, defaultUserAgent, r.UserAgent(), "Expected '%s', got '%s'", defaultUserAgent, r.UserAgent()) fmt.Fprint(w, expect) })) defer srv.Close() g, err := All(cli.New()).ByScheme("http") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) got, err := g.Get(srv.URL, WithURL(srv.URL)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if got.String() != expect { - t.Errorf("Expected %q, got %q", expect, got.String()) - } + assert.Equal(t, expect, got.String(), "Expected %q, got %q", expect, got.String()) // test with http server const expectedUserAgent = "I am Groot" @@ -182,9 +125,7 @@ func TestDownload(t *testing.T) { if !ok || username != "username" || password != "password" { t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) } - if r.UserAgent() != expectedUserAgent { - t.Errorf("Expected '%s', got '%s'", expectedUserAgent, r.UserAgent()) - } + assert.Equal(t, expectedUserAgent, r.UserAgent(), "Expected '%s', got '%s'", expectedUserAgent, r.UserAgent()) fmt.Fprint(w, expect) })) @@ -197,17 +138,11 @@ func TestDownload(t *testing.T) { WithPassCredentialsAll(false), WithUserAgent(expectedUserAgent), ) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) got, err = httpgetter.Get(u.String()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if got.String() != expect { - t.Errorf("Expected %q, got %q", expect, got.String()) - } + assert.Equal(t, expect, got.String(), "Expected %q, got %q", expect, got.String()) // test with Get URL differing from withURL crossAuthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -232,17 +167,11 @@ func TestDownload(t *testing.T) { WithBasicAuth("username", "password"), WithPassCredentialsAll(false), ) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) got, err = httpgetter.Get(u.String()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if got.String() != expect { - t.Errorf("Expected %q, got %q", expect, got.String()) - } + assert.Equal(t, expect, got.String(), "Expected %q, got %q", expect, got.String()) // test with Get URL differing from withURL and should pass creds crossAuthSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -267,24 +196,16 @@ func TestDownload(t *testing.T) { WithBasicAuth("username", "password"), WithPassCredentialsAll(true), ) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) got, err = httpgetter.Get(u.String()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if got.String() != expect { - t.Errorf("Expected %q, got %q", expect, got.String()) - } + assert.Equal(t, expect, got.String(), "Expected %q, got %q", expect, got.String()) // test server with varied Accept Header const expectedAcceptHeader = "application/gzip,application/octet-stream" acceptHeaderSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("Accept") != expectedAcceptHeader { - t.Errorf("Expected '%s', got '%s'", expectedAcceptHeader, r.Header.Get("Accept")) - } + assert.Equal(t, expectedAcceptHeader, r.Header.Get("Accept"), "Expected '%s', got '%s'", expectedAcceptHeader, r.Header.Get("Accept")) fmt.Fprint(w, expect) })) @@ -294,13 +215,9 @@ func TestDownload(t *testing.T) { httpgetter, err = NewHTTPGetter( WithAcceptHeader(expectedAcceptHeader), ) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) _, err = httpgetter.Get(u.String()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } func TestDownloadTLS(t *testing.T) { @@ -314,9 +231,7 @@ func TestDownloadTLS(t *testing.T) { tlsutil.WithCertKeyPairFiles(pub, priv), tlsutil.WithCAFile(ca), ) - if err != nil { - t.Fatal(fmt.Errorf("can't create TLS config for client: %w", err)) - } + require.NoError(t, err, "can't create TLS config for client") tlsConf.ServerName = "helm.sh" tlsSrv.TLS = tlsConf tlsSrv.StartTLS() @@ -327,33 +242,24 @@ func TestDownloadTLS(t *testing.T) { WithURL(u.String()), WithTLSClientConfig(pub, priv, ca), ) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if _, err := g.Get(u.String()); err != nil { - t.Error(err) - } + _, err = g.Get(u.String()) + require.NoError(t, err) // now test with TLS config being passed along in .Get (see #6635) g, err = NewHTTPGetter() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if _, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig(pub, priv, ca)); err != nil { - t.Error(err) - } + _, err = g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig(pub, priv, ca)) + require.NoError(t, err) // test with only the CA file (see also #6635) g, err = NewHTTPGetter() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if _, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig("", "", ca)); err != nil { - t.Error(err) - } + _, err = g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig("", "", ca)) + assert.NoError(t, err) } func TestDownloadTLSWithRedirect(t *testing.T) { @@ -369,9 +275,7 @@ func TestDownloadTLSWithRedirect(t *testing.T) { tlsutil.WithInsecureSkipVerify(insecureSkipTLSVerify), ) - if err != nil { - t.Fatal(fmt.Errorf("can't create TLS config for client: %w", err)) - } + require.NoError(t, err, "can't create TLS config for client") tlsSrv2 := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { rw.Header().Set("Content-Type", "text/plain") @@ -390,9 +294,7 @@ func TestDownloadTLSWithRedirect(t *testing.T) { tlsutil.WithInsecureSkipVerify(insecureSkipTLSVerify), ) - if err != nil { - t.Fatal(fmt.Errorf("can't create TLS config for client: %w", err)) - } + require.NoError(t, err, "can't create TLS config for client") tlsSrv1 := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { u, _ := url.ParseRequestURI(tlsSrv2.URL) @@ -419,65 +321,41 @@ func TestDownloadTLSWithRedirect(t *testing.T) { WithURL(u.String()), WithTLSClientConfig(pub, priv, ca), ) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) buf, err := g.Get(u.String()) - if err != nil { - t.Error(err) - } + require.NoError(t, err) b, err := io.ReadAll(buf) - if err != nil { - t.Error(err) - } + require.NoError(t, err) - if string(b) != srv2Resp { - t.Errorf("expected response from Server2 to be '%s', instead got: %s", srv2Resp, string(b)) - } + assert.Equal(t, srv2Resp, string(b), "expected response from Server2 to be '%s', instead got: %s", srv2Resp, string(b)) }) t.Run("Test with TLS config being passed along in .Get (see #6635)", func(t *testing.T) { g, err := NewHTTPGetter() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) buf, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig(pub, priv, ca)) - if err != nil { - t.Error(err) - } + require.NoError(t, err) b, err := io.ReadAll(buf) - if err != nil { - t.Error(err) - } + require.NoError(t, err) - if string(b) != srv2Resp { - t.Errorf("expected response from Server2 to be '%s', instead got: %s", srv2Resp, string(b)) - } + assert.Equal(t, srv2Resp, string(b), "expected response from Server2 to be '%s', instead got: %s", srv2Resp, string(b)) }) t.Run("Test with only the CA file (see also #6635)", func(t *testing.T) { g, err := NewHTTPGetter() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) buf, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig("", "", ca)) - if err != nil { - t.Error(err) - } + require.NoError(t, err) b, err := io.ReadAll(buf) - if err != nil { - t.Error(err) - } + require.NoError(t, err) - if string(b) != srv2Resp { - t.Errorf("expected response from Server2 to be '%s', instead got: %s", srv2Resp, string(b)) - } + assert.Equal(t, srv2Resp, string(b), "expected response from Server2 to be '%s', instead got: %s", srv2Resp, string(b)) }) } @@ -491,25 +369,19 @@ func TestDownloadInsecureSkipTLSVerify(t *testing.T) { g, err := NewHTTPGetter( WithURL(u.String()), ) - if err != nil { - t.Error(err) - } + require.NoError(t, err) - if _, err := g.Get(u.String()); err == nil { - t.Errorf("Expected Getter to throw an error, got %s", err) - } + _, err = g.Get(u.String()) + require.Errorf(t, err, "Expected Getter to throw an error") // Test certificate check skip g, err = NewHTTPGetter( WithURL(u.String()), WithInsecureSkipVerifyTLS(true), ) - if err != nil { - t.Error(err) - } - if _, err = g.Get(u.String()); err != nil { - t.Error(err) - } + require.NoError(t, err) + _, err = g.Get(u.String()) + assert.NoError(t, err) } func TestHTTPGetterTarDownload(t *testing.T) { @@ -535,17 +407,13 @@ func TestHTTPGetterTarDownload(t *testing.T) { defer srv.Close() g, err := NewHTTPGetter(WithURL(srv.URL)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) data, _ := g.Get(srv.URL) mimeType := http.DetectContentType(data.Bytes()) expectedMimeType := "application/x-gzip" - if mimeType != expectedMimeType { - t.Fatalf("Expected response with MIME type %s, but got %s", expectedMimeType, mimeType) - } + require.Equal(t, expectedMimeType, mimeType, "Expected response with MIME type %s, but got %s", expectedMimeType, mimeType) } func TestHttpClientInsecureSkipVerify(t *testing.T) { @@ -569,31 +437,22 @@ func TestHttpClientInsecureSkipVerify(t *testing.T) { g.opts.keyFile = "testdata/client.key" g.opts.insecureSkipVerifyTLS = true transport := verifyInsecureSkipVerify(t, &g, "HTTPGetter with 2 way ssl", true) - if len(transport.TLSClientConfig.Certificates) == 0 { - t.Fatal("transport.TLSClientConfig.Certificates is not present") - } + require.NotEmpty(t, transport.TLSClientConfig.Certificates, "transport.TLSClientConfig.Certificates is not present") } func verifyInsecureSkipVerify(t *testing.T, g *HTTPGetter, caseName string, expectedValue bool) *http.Transport { t.Helper() returnVal, err := g.httpClient(g.opts) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if returnVal == nil { - t.Fatal("Expected non nil value for http client") - } + require.NotNil(t, returnVal, "Expected non nil value for http client") transport := (returnVal.Transport).(*http.Transport) gotValue := false if transport.TLSClientConfig != nil { gotValue = transport.TLSClientConfig.InsecureSkipVerify } - if gotValue != expectedValue { - t.Fatalf("Case Name = %s\nInsecureSkipVerify did not come as expected. Expected = %t; Got = %v", - caseName, expectedValue, gotValue) - } + require.Equal(t, expectedValue, gotValue, "Case Name = %s\nInsecureSkipVerify did not come as expected. Expected = %t; Got = %v", caseName, expectedValue, gotValue) return transport } @@ -602,31 +461,21 @@ func TestDefaultHTTPTransportReuse(t *testing.T) { httpClient1, err := g.httpClient(g.opts) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if httpClient1 == nil { - t.Fatal("Expected non nil value for http client") - } + require.NotNil(t, httpClient1, "Expected non nil value for http client") transport1 := (httpClient1.Transport).(*http.Transport) httpClient2, err := g.httpClient(g.opts) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if httpClient2 == nil { - t.Fatal("Expected non nil value for http client") - } + require.NotNil(t, httpClient2, "Expected non nil value for http client") transport2 := (httpClient2.Transport).(*http.Transport) - if transport1 != transport2 { - t.Fatal("Expected default transport to be reused") - } + require.Equal(t, transport2, transport1, "Expected default transport to be reused") } func TestHTTPTransportOption(t *testing.T) { @@ -636,35 +485,23 @@ func TestHTTPTransportOption(t *testing.T) { g.opts.transport = transport httpClient1, err := g.httpClient(g.opts) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if httpClient1 == nil { - t.Fatal("Expected non nil value for http client") - } + require.NotNil(t, httpClient1, "Expected non nil value for http client") transport1 := (httpClient1.Transport).(*http.Transport) - if transport1 != transport { - t.Fatal("Expected transport option to be applied") - } + require.Equal(t, transport, transport1, "Expected transport option to be applied") httpClient2, err := g.httpClient(g.opts) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if httpClient2 == nil { - t.Fatal("Expected non nil value for http client") - } + require.NotNil(t, httpClient2, "Expected non nil value for http client") transport2 := (httpClient2.Transport).(*http.Transport) - if transport1 != transport2 { - t.Fatal("Expected applied transport to be reused") - } + require.Equal(t, transport2, transport1, "Expected applied transport to be reused") g = HTTPGetter{} g.opts.url = "https://localhost" @@ -673,7 +510,5 @@ func TestHTTPTransportOption(t *testing.T) { g.opts.insecureSkipVerifyTLS = true g.opts.transport = transport usedTransport := verifyInsecureSkipVerify(t, &g, "HTTPGetter with 2 way ssl", false) - if usedTransport.TLSClientConfig != nil { - t.Fatal("transport.TLSClientConfig should not be set") - } + require.Nil(t, usedTransport.TLSClientConfig, "transport.TLSClientConfig should not be set") } From ca1767b81397e5ee3b3a5f7926590ddf1134c9c5 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:55 +0200 Subject: [PATCH 463/571] chore(pkg): refactor: convert tests to testify assert/require part 7 refactor: convert tests to testify assert/require in pkg Signed-off-by: Matthieu MOREL --- pkg/chart/v2/util/save_test.go | 133 +++++------------- pkg/cmd/upgrade_test.go | 250 +++++++++------------------------ 2 files changed, 104 insertions(+), 279 deletions(-) diff --git a/pkg/chart/v2/util/save_test.go b/pkg/chart/v2/util/save_test.go index 55c67f49e..1795226bc 100644 --- a/pkg/chart/v2/util/save_test.go +++ b/pkg/chart/v2/util/save_test.go @@ -37,6 +37,7 @@ import ( chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/chart/v2/loader" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -62,29 +63,16 @@ func TestSave(t *testing.T) { chartWithInvalidJSON := withSchema(*c, []byte("{")) where, err := Save(c, dest) - if err != nil { - t.Fatalf("Failed to save: %s", err) - } - if !strings.HasPrefix(where, dest) { - t.Fatalf("Expected %q to start with %q", where, dest) - } - if !strings.HasSuffix(where, ".tgz") { - t.Fatalf("Expected %q to end with .tgz", where) - } + require.NoError(t, err, "Failed to save") + require.Truef(t, strings.HasPrefix(where, dest), "Expected %q to start with %q", where, dest) + require.Truef(t, strings.HasSuffix(where, ".tgz"), "Expected %q to end with .tgz", where) c2, err := loader.LoadFile(where) - if err != nil { - t.Fatal(err) - } - if c2.Name() != c.Name() { - t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name()) - } - if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" { - t.Fatal("Files data did not match") - } - if c2.Lock != nil { - t.Fatal("Expected v1 chart archive not to contain Chart.lock file") - } + require.NoError(t, err) + require.Equal(t, c.Name(), c2.Name(), "Expected chart archive to have %q, got %q", c.Name(), c2.Name()) + require.Len(t, c2.Files, 1, "Files data did not match") + require.Equal(t, "scheherazade/shahryar.txt", c2.Files[0].Name, "Files data did not match") + require.Nil(t, c2.Lock, "Expected v1 chart archive not to contain Chart.lock file") if !bytes.Equal(c.Schema, c2.Schema) { indentation := 4 @@ -92,25 +80,16 @@ func TestSave(t *testing.T) { formattedActual := Indent(indentation, string(c2.Schema)) t.Fatalf("Schema data did not match.\nExpected:\n%s\nActual:\n%s", formattedExpected, formattedActual) } - if _, err := Save(&chartWithInvalidJSON, dest); err == nil { - t.Fatal("Invalid JSON was not caught while saving chart") - } + _, err = Save(&chartWithInvalidJSON, dest) + require.Error(t, err, "Invalid JSON was not caught while saving chart") c.Metadata.APIVersion = chart.APIVersionV2 where, err = Save(c, dest) - if err != nil { - t.Fatalf("Failed to save: %s", err) - } + require.NoError(t, err, "Failed to save") c2, err = loader.LoadFile(where) - if err != nil { - t.Fatal(err) - } - if c2.Lock == nil { - t.Fatal("Expected v2 chart archive to contain a Chart.lock file") - } - if c2.Lock.Digest != c.Lock.Digest { - t.Fatal("Chart.lock data did not match") - } + require.NoError(t, err) + require.NotNil(t, c2.Lock, "Expected v2 chart archive to contain a Chart.lock file") + require.Equal(t, c.Lock.Digest, c2.Lock.Digest, "Chart.lock data did not match") }) } @@ -128,9 +107,7 @@ func TestSave(t *testing.T) { }, } _, err := Save(c, tmp) - if err == nil { - t.Fatal("Expected error saving chart with invalid name") - } + require.Error(t, err, "Expected error saving chart with invalid name") } // https://github.com/helm/helm/issues/31844 @@ -145,20 +122,14 @@ func TestSavedGzipExtraFieldIsValid(t *testing.T) { } where, err := Save(c, tmp) - if err != nil { - t.Fatalf("Failed to save: %s", err) - } + require.NoError(t, err, "Failed to save") f, err := os.Open(where) - if err != nil { - t.Fatalf("Failed to open saved file: %s", err) - } + require.NoError(t, err, "Failed to open saved file") defer f.Close() r, err := gzip.NewReader(f) - if err != nil { - t.Fatalf("Failed to create gzip reader: %s", err) - } + require.NoError(t, err, "Failed to create gzip reader") defer r.Close() // RFC 1952 §2.3.1.1: @@ -214,20 +185,14 @@ func TestSavePreservesTimestamps(t *testing.T) { } where, err := Save(c, tmp) - if err != nil { - t.Fatalf("Failed to save: %s", err) - } + require.NoError(t, err, "Failed to save") allHeaders, err := retrieveAllHeadersFromTar(where) - if err != nil { - t.Fatalf("Failed to parse tar: %v", err) - } + require.NoError(t, err, "Failed to parse tar") roundedTime := initialCreateTime.Round(time.Second) for _, header := range allHeaders { - if !header.ModTime.Equal(roundedTime) { - t.Fatalf("File timestamp not preserved: %v", header.ModTime) - } + require.Truef(t, header.ModTime.Equal(roundedTime), "File timestamp not preserved: %v", header.ModTime) } } @@ -248,20 +213,14 @@ func TestSaveWithSourceDateEpoch(t *testing.T) { c.StampModTimes(epoch) where, err := Save(c, tmp) - if err != nil { - t.Fatalf("Failed to save: %s", err) - } + require.NoError(t, err, "Failed to save") allHeaders, err := retrieveAllHeadersFromTar(where) - if err != nil { - t.Fatalf("Failed to parse tar: %v", err) - } + require.NoError(t, err, "Failed to parse tar") expected := epoch.Round(time.Second) for _, header := range allHeaders { - if !header.ModTime.Equal(expected) { - t.Fatalf("Expected SOURCE_DATE_EPOCH timestamp %v, got %v for %q", expected, header.ModTime, header.Name) - } + require.Truef(t, header.ModTime.Equal(expected), "Expected SOURCE_DATE_EPOCH timestamp %v, got %v for %q", expected, header.ModTime, header.Name) } } @@ -317,37 +276,25 @@ func TestSaveDir(t *testing.T) { }, } - if err := SaveDir(c, tmp); err != nil { - t.Fatalf("Failed to save: %s", err) - } + require.NoErrorf(t, SaveDir(c, tmp), "Failed to save") c2, err := loader.LoadDir(tmp + "/ahab") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if c2.Name() != c.Name() { - t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name()) - } + require.Equal(t, c.Name(), c2.Name(), "Expected chart archive to have %q, got %q", c.Name(), c2.Name()) - if len(c2.Templates) != 1 || c2.Templates[0].Name != c.Templates[0].Name { - t.Fatal("Templates data did not match") - } + require.Len(t, c2.Templates, 1, "Templates data did not match") + require.Equal(t, c2.Templates[0].Name, c.Templates[0].Name, "Templates data did not match") - if len(c2.Files) != 1 || c2.Files[0].Name != c.Files[0].Name { - t.Fatal("Files data did not match") - } + require.Len(t, c2.Files, 1, "Files data did not match") + require.Equal(t, c2.Files[0].Name, c.Files[0].Name, "Files data did not match") tmp2 := t.TempDir() c.Metadata.Name = "../ahab" pth := filepath.Join(tmp2, "tmpcharts") - if err := os.MkdirAll(filepath.Join(pth), 0o755); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(filepath.Join(pth), 0o755)) - if err := SaveDir(c, pth); err.Error() != "\"../ahab\" is not a valid chart name" { - t.Fatalf("Did not get expected error for chart named %q", c.Name()) - } + require.EqualErrorf(t, SaveDir(c, pth), "\"../ahab\" is not a valid chart name", "Did not get expected error for chart named %q", c.Name()) } func TestRepeatableSave(t *testing.T) { @@ -408,18 +355,12 @@ func TestRepeatableSave(t *testing.T) { // create package dest := path.Join(tmp, "newdir") where, err := Save(test.chart, dest) - if err != nil { - t.Fatalf("Failed to save: %s", err) - } + require.NoError(t, err, "Failed to save") // get shasum for package result, err := sha256Sum(where) - if err != nil { - t.Fatalf("Failed to check shasum: %s", err) - } + require.NoError(t, err, "Failed to check shasum") // assert that the package SHA is what we wanted. - if result != test.want { - t.Errorf("FormatName() result = %v, want %v", result, test.want) - } + assert.Equal(t, test.want, result, "FormatName() result = %v, want %v", result, test.want) }) } } diff --git a/pkg/cmd/upgrade_test.go b/pkg/cmd/upgrade_test.go index 30188d3d9..ab6c43235 100644 --- a/pkg/cmd/upgrade_test.go +++ b/pkg/cmd/upgrade_test.go @@ -21,10 +21,12 @@ import ( "os" "path/filepath" "reflect" - "strings" "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/chart/v2/loader" @@ -44,13 +46,9 @@ func TestUpgradeCmd(t *testing.T) { }, } chartPath := filepath.Join(tmpChart, cfile.Metadata.Name) - if err := chartutil.SaveDir(cfile, tmpChart); err != nil { - t.Fatalf("Error creating chart for upgrade: %v", err) - } + require.NoErrorf(t, chartutil.SaveDir(cfile, tmpChart), "Error creating chart for upgrade") ch, err := loader.Load(chartPath) - if err != nil { - t.Fatalf("Error loading chart: %v", err) - } + require.NoError(t, err, "Error loading chart") _ = release.Mock(&release.MockReleaseOptions{ Name: "funny-bunny", Chart: ch, @@ -59,25 +57,17 @@ func TestUpgradeCmd(t *testing.T) { // update chart version cfile.Metadata.Version = "0.1.2" - if err := chartutil.SaveDir(cfile, tmpChart); err != nil { - t.Fatalf("Error creating chart: %v", err) - } + require.NoErrorf(t, chartutil.SaveDir(cfile, tmpChart), "Error creating chart") ch, err = loader.Load(chartPath) - if err != nil { - t.Fatalf("Error loading updated chart: %v", err) - } + require.NoError(t, err, "Error loading updated chart") // update chart version again cfile.Metadata.Version = "0.1.3" - if err := chartutil.SaveDir(cfile, tmpChart); err != nil { - t.Fatalf("Error creating chart: %v", err) - } + require.NoErrorf(t, chartutil.SaveDir(cfile, tmpChart), "Error creating chart") var ch2 *chart.Chart ch2, err = loader.Load(chartPath) - if err != nil { - t.Fatalf("Error loading updated chart: %v", err) - } + require.NoError(t, err, "Error loading updated chart") missingDepsPath := "testdata/testcharts/chart-missing-deps" badDepsPath := "testdata/testcharts/chart-bad-requirements" @@ -205,22 +195,14 @@ func TestUpgradeWithValue(t *testing.T) { cmd := fmt.Sprintf("upgrade %s --set favoriteDrink=tea '%s'", releaseName, chartPath) _, _, err := executeActionCommandC(store, cmd) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) updatedReli, err := store.Get(releaseName, 4) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } - updatedRel, err := releaserToV1Release(updatedReli) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) - if !strings.Contains(updatedRel.Manifest, "drink: tea") { - t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest) - } + updatedRel, err := releaserToV1Release(updatedReli) + require.NoError(t, err) + assert.Contains(t, updatedRel.Manifest, "drink: tea", "The value is not set correctly. manifest: %s", updatedRel.Manifest) } func TestUpgradeWithStringValue(t *testing.T) { @@ -235,22 +217,14 @@ func TestUpgradeWithStringValue(t *testing.T) { cmd := fmt.Sprintf("upgrade %s --set-string favoriteDrink=coffee '%s'", releaseName, chartPath) _, _, err := executeActionCommandC(store, cmd) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) updatedReli, err := store.Get(releaseName, 4) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } - updatedRel, err := releaserToV1Release(updatedReli) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) - if !strings.Contains(updatedRel.Manifest, "drink: coffee") { - t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest) - } + updatedRel, err := releaserToV1Release(updatedReli) + require.NoError(t, err) + assert.Contains(t, updatedRel.Manifest, "drink: coffee", "The value is not set correctly. manifest: %s", updatedRel.Manifest) } func TestUpgradeInstallWithSubchartNotes(t *testing.T) { @@ -265,26 +239,15 @@ func TestUpgradeInstallWithSubchartNotes(t *testing.T) { cmd := fmt.Sprintf("upgrade %s -i --render-subchart-notes '%s'", releaseName, "testdata/testcharts/chart-with-subchart-notes") _, _, err := executeActionCommandC(store, cmd) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) upgradedReli, err := store.Get(releaseName, 2) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } - upgradedRel, err := releaserToV1Release(upgradedReli) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) - if !strings.Contains(upgradedRel.Info.Notes, "PARENT NOTES") { - t.Errorf("The parent notes are not set correctly. NOTES: %s", upgradedRel.Info.Notes) - } - - if !strings.Contains(upgradedRel.Info.Notes, "SUBCHART NOTES") { - t.Errorf("The subchart notes are not set correctly. NOTES: %s", upgradedRel.Info.Notes) - } + upgradedRel, err := releaserToV1Release(upgradedReli) + require.NoError(t, err) + assert.Contains(t, upgradedRel.Info.Notes, "PARENT NOTES", "The parent notes are not set correctly. NOTES: %s", upgradedRel.Info.Notes) + assert.Contains(t, upgradedRel.Info.Notes, "SUBCHART NOTES", "The subchart notes are not set correctly. NOTES: %s", upgradedRel.Info.Notes) } func TestUpgradeWithValuesFile(t *testing.T) { @@ -299,22 +262,14 @@ func TestUpgradeWithValuesFile(t *testing.T) { cmd := fmt.Sprintf("upgrade %s --values testdata/testcharts/upgradetest/values.yaml '%s'", releaseName, chartPath) _, _, err := executeActionCommandC(store, cmd) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) updatedReli, err := store.Get(releaseName, 4) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } - updatedRel, err := releaserToV1Release(updatedReli) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) - if !strings.Contains(updatedRel.Manifest, "drink: beer") { - t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest) - } + updatedRel, err := releaserToV1Release(updatedReli) + require.NoError(t, err) + assert.Contains(t, updatedRel.Manifest, "drink: beer", "The value is not set correctly. manifest: %s", updatedRel.Manifest) } func TestUpgradeWithValuesFromStdin(t *testing.T) { @@ -328,28 +283,18 @@ func TestUpgradeWithValuesFromStdin(t *testing.T) { store.Create(relMock(releaseName, 3, ch)) in, err := os.Open("testdata/testcharts/upgradetest/values.yaml") - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) cmd := fmt.Sprintf("upgrade %s --values - '%s'", releaseName, chartPath) _, _, err = executeActionCommandStdinC(store, in, cmd) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) updatedReli, err := store.Get(releaseName, 4) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } - updatedRel, err := releaserToV1Release(updatedReli) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) - if !strings.Contains(updatedRel.Manifest, "drink: beer") { - t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest) - } + updatedRel, err := releaserToV1Release(updatedReli) + require.NoError(t, err) + assert.Contains(t, updatedRel.Manifest, "drink: beer", "The value is not set correctly. manifest: %s", updatedRel.Manifest) } func TestUpgradeInstallWithValuesFromStdin(t *testing.T) { @@ -361,37 +306,25 @@ func TestUpgradeInstallWithValuesFromStdin(t *testing.T) { store := storageFixture() in, err := os.Open("testdata/testcharts/upgradetest/values.yaml") - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) cmd := fmt.Sprintf("upgrade %s -f - --install '%s'", releaseName, chartPath) _, _, err = executeActionCommandStdinC(store, in, cmd) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) updatedReli, err := store.Get(releaseName, 1) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } - updatedRel, err := releaserToV1Release(updatedReli) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) - if !strings.Contains(updatedRel.Manifest, "drink: beer") { - t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest) - } + updatedRel, err := releaserToV1Release(updatedReli) + require.NoError(t, err) + assert.Contains(t, updatedRel.Manifest, "drink: beer", "The value is not set correctly. manifest: %s", updatedRel.Manifest) } func prepareMockRelease(t *testing.T, releaseName string) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) { t.Helper() tmpChart := t.TempDir() configmapData, err := os.ReadFile("testdata/testcharts/upgradetest/templates/configmap.yaml") - if err != nil { - t.Fatalf("Error loading template yaml %v", err) - } + require.NoError(t, err, "Error loading template yaml") cfile := &chart.Chart{ Metadata: &chart.Metadata{ APIVersion: chart.APIVersionV1, @@ -402,13 +335,9 @@ func prepareMockRelease(t *testing.T, releaseName string) (func(n string, v int, Templates: []*common.File{{Name: "templates/configmap.yaml", ModTime: time.Now(), Data: configmapData}}, } chartPath := filepath.Join(tmpChart, cfile.Metadata.Name) - if err := chartutil.SaveDir(cfile, tmpChart); err != nil { - t.Fatalf("Error creating chart for upgrade: %v", err) - } + require.NoErrorf(t, chartutil.SaveDir(cfile, tmpChart), "Error creating chart for upgrade") ch, err := loader.Load(chartPath) - if err != nil { - t.Fatalf("Error loading chart: %v", err) - } + require.NoError(t, err, "Error loading chart") _ = release.Mock(&release.MockReleaseOptions{ Name: releaseName, Chart: ch, @@ -475,35 +404,23 @@ func TestUpgradeInstallWithLabels(t *testing.T) { } cmd := fmt.Sprintf("upgrade %s --install --labels key1=val1,key2=val2 '%s'", releaseName, chartPath) _, _, err := executeActionCommandC(store, cmd) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) updatedReli, err := store.Get(releaseName, 1) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } - updatedRel, err := releaserToV1Release(updatedReli) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) - if !reflect.DeepEqual(updatedRel.Labels, expectedLabels) { - t.Errorf("Expected {%v}, got {%v}", expectedLabels, updatedRel.Labels) - } + updatedRel, err := releaserToV1Release(updatedReli) + require.NoError(t, err) + assert.Truef(t, reflect.DeepEqual(updatedRel.Labels, expectedLabels), "Expected {%v}, got {%v}", expectedLabels, updatedRel.Labels) } func prepareMockReleaseWithSecret(t *testing.T, releaseName string) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) { t.Helper() tmpChart := t.TempDir() configmapData, err := os.ReadFile("testdata/testcharts/chart-with-secret/templates/configmap.yaml") - if err != nil { - t.Fatalf("Error loading template yaml %v", err) - } + require.NoError(t, err, "Error loading template yaml") secretData, err := os.ReadFile("testdata/testcharts/chart-with-secret/templates/secret.yaml") - if err != nil { - t.Fatalf("Error loading template yaml %v", err) - } + require.NoError(t, err, "Error loading template yaml") modTime := time.Now() cfile := &chart.Chart{ Metadata: &chart.Metadata{ @@ -515,13 +432,9 @@ func prepareMockReleaseWithSecret(t *testing.T, releaseName string) (func(n stri Templates: []*common.File{{Name: "templates/configmap.yaml", ModTime: modTime, Data: configmapData}, {Name: "templates/secret.yaml", ModTime: modTime, Data: secretData}}, } chartPath := filepath.Join(tmpChart, cfile.Metadata.Name) - if err := chartutil.SaveDir(cfile, tmpChart); err != nil { - t.Fatalf("Error creating chart for upgrade: %v", err) - } + require.NoErrorf(t, chartutil.SaveDir(cfile, tmpChart), "Error creating chart for upgrade") ch, err := loader.Load(chartPath) - if err != nil { - t.Fatalf("Error loading chart: %v", err) - } + require.NoError(t, err, "Error loading chart") _ = release.Mock(&release.MockReleaseOptions{ Name: releaseName, Chart: ch, @@ -546,54 +459,34 @@ func TestUpgradeWithDryRun(t *testing.T) { // have it available. cmd := fmt.Sprintf("upgrade %s --install '%s'", releaseName, chartPath) _, _, err := executeActionCommandC(store, cmd) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) _, err = store.Get(releaseName, 1) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) cmd = fmt.Sprintf("upgrade %s --dry-run '%s'", releaseName, chartPath) _, out, err := executeActionCommandC(store, cmd) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) // No second release should be stored because this is a dry run. _, err = store.Get(releaseName, 2) - if err == nil { - t.Error("expected error as there should be no new release but got none") - } - - if !strings.Contains(out, "kind: Secret") { - t.Error("expected secret in output from --dry-run but found none") - } + require.Error(t, err, "expected error as there should be no new release but got none") + assert.Contains(t, out, "kind: Secret", "expected secret in output from --dry-run but found none") // Ensure the secret is not in the output cmd = fmt.Sprintf("upgrade %s --dry-run --hide-secret '%s'", releaseName, chartPath) _, out, err = executeActionCommandC(store, cmd) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) // No second release should be stored because this is a dry run. _, err = store.Get(releaseName, 2) - if err == nil { - t.Error("expected error as there should be no new release but got none") - } - - if strings.Contains(out, "kind: Secret") { - t.Error("expected no secret in output from --dry-run --hide-secret but found one") - } + require.Error(t, err, "expected error as there should be no new release but got none") + assert.NotContains(t, out, "kind: Secret", "expected no secret in output from --dry-run --hide-secret but found one") // Ensure there is an error when --hide-secret used without dry-run cmd = fmt.Sprintf("upgrade %s --hide-secret '%s'", releaseName, chartPath) _, _, err = executeActionCommandC(store, cmd) - if err == nil { - t.Error("expected error when --hide-secret used without --dry-run") - } + assert.Error(t, err, "expected error when --hide-secret used without --dry-run") } func TestUpgradeInstallServerSideApply(t *testing.T) { @@ -630,23 +523,14 @@ func TestUpgradeInstallServerSideApply(t *testing.T) { cmd := fmt.Sprintf("upgrade %s --install %s '%s'", releaseName, tt.serverSideFlag, chartPath) _, _, err := executeActionCommandC(store, cmd) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } + require.NoError(t, err) rel, err := store.Get(releaseName, 1) - if err != nil { - t.Fatalf("unexpected error getting release: %v", err) - } + require.NoError(t, err, "unexpected error getting release") relV1, err := releaserToV1Release(rel) - if err != nil { - t.Fatalf("unexpected error converting release: %v", err) - } - - if relV1.ApplyMethod != tt.expectedApplyMethod { - t.Errorf("expected ApplyMethod %q, got %q", tt.expectedApplyMethod, relV1.ApplyMethod) - } + require.NoError(t, err, "unexpected error converting release") + assert.Equal(t, tt.expectedApplyMethod, relV1.ApplyMethod, "expected ApplyMethod %q, got %q", tt.expectedApplyMethod, relV1.ApplyMethod) }) } } From e5f0e07b96580305c3902eceb738cef230b44bec Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:55 +0200 Subject: [PATCH 464/571] chore(pkg): refactor: convert tests to testify assert/require part 8 refactor: convert tests to testify assert/require in pkg Signed-off-by: Matthieu MOREL --- pkg/chart/v2/chart_test.go | 10 +- pkg/kube/statuswait_test.go | 168 +++++++++++------------------ pkg/storage/driver/cfgmaps_test.go | 115 ++++++-------------- 3 files changed, 98 insertions(+), 195 deletions(-) diff --git a/pkg/chart/v2/chart_test.go b/pkg/chart/v2/chart_test.go index 813669366..313d65480 100644 --- a/pkg/chart/v2/chart_test.go +++ b/pkg/chart/v2/chart_test.go @@ -21,6 +21,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/chart/common" ) @@ -76,15 +77,12 @@ func TestSaveChartNoRawData(t *testing.T) { } is := assert.New(t) + req := require.New(t) data, err := json.Marshal(chrt) - if err != nil { - t.Fatal(err) - } + req.NoError(err) res := &Chart{} - if err := json.Unmarshal(data, res); err != nil { - t.Fatal(err) - } + req.NoError(json.Unmarshal(data, res)) is.Equal([]*common.File(nil), res.Raw) } diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index 21580b57a..5f5f5d051 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -272,8 +272,7 @@ func getRuntimeObjFromManifests(t *testing.T, manifests []string) []runtime.Obje objects := []runtime.Object{} for _, manifest := range manifests { m := make(map[string]any) - err := yaml.Unmarshal([]byte(manifest), &m) - require.NoError(t, err) + require.NoError(t, yaml.Unmarshal([]byte(manifest), &m)) resource := &unstructured.Unstructured{Object: m} objects = append(objects, resource) } @@ -332,8 +331,7 @@ func TestStatusWaitForDelete(t *testing.T) { for _, objToCreate := range objsToCreate { u := objToCreate.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } objsToDelete := getRuntimeObjFromManifests(t, tt.manifestsToDelete) for _, objToDelete := range objsToDelete { @@ -341,8 +339,7 @@ func TestStatusWaitForDelete(t *testing.T) { gvr := getGVR(t, fakeMapper, u) go func(gvr schema.GroupVersionResource, u *unstructured.Unstructured) { time.Sleep(timeUntilPodDelete) - err := fakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName()) - assert.NoError(t, err) + assert.NoError(t, fakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName())) }(gvr, u) } resourceList := getResourceListFromRuntimeObjs(t, c, objsToCreate) @@ -352,9 +349,9 @@ func TestStatusWaitForDelete(t *testing.T) { for _, expectedErrStr := range tt.expectErrs { require.ErrorContains(t, err, expectedErrStr) } - return + } else { + assert.NoError(t, err) } - assert.NoError(t, err) }) } } @@ -375,8 +372,7 @@ func TestStatusWaitForDeleteNonExistentObject(t *testing.T) { // Don't create the object to test that the wait for delete works when the object doesn't exist objManifest := getRuntimeObjFromManifests(t, []string{podCurrentManifest}) resourceList := getResourceListFromRuntimeObjs(t, c, objManifest) - err := statusWaiter.WaitForDelete(resourceList, timeout) - assert.NoError(t, err) + assert.NoError(t, statusWaiter.WaitForDelete(resourceList, timeout)) } func TestStatusWait(t *testing.T) { @@ -433,8 +429,7 @@ func TestStatusWait(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.Wait(resourceList, time.Second*3) @@ -443,9 +438,9 @@ func TestStatusWait(t *testing.T) { for _, expectedErrStr := range tt.expectErrStrs { require.ErrorContains(t, err, expectedErrStr) } - return + } else { + assert.NoError(t, err) } - assert.NoError(t, err) }) } } @@ -490,8 +485,7 @@ func TestWaitForJobComplete(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.WaitWithJobs(resourceList, time.Second*3) @@ -500,9 +494,9 @@ func TestWaitForJobComplete(t *testing.T) { for _, expectedErrStr := range tt.expectErrStrs { require.ErrorContains(t, err, expectedErrStr) } - return + } else { + assert.NoError(t, err) } - assert.NoError(t, err) }) } } @@ -553,8 +547,7 @@ func TestWatchForReady(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.WatchUntilReady(resourceList, time.Second*3) @@ -563,9 +556,9 @@ func TestWatchForReady(t *testing.T) { for _, expectedErrStr := range tt.expectErrStrs { require.ErrorContains(t, err, expectedErrStr) } - return + } else { + assert.NoError(t, err) } - assert.NoError(t, err) }) } } @@ -657,8 +650,7 @@ func TestStatusWaitMultipleNamespaces(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } if strings.Contains(tt.name, "delete") { @@ -668,8 +660,7 @@ func TestStatusWaitMultipleNamespaces(t *testing.T) { gvr := getGVR(t, fakeMapper, u) go func(gvr schema.GroupVersionResource, u *unstructured.Unstructured) { time.Sleep(timeUntilDelete) - err := fakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName()) - assert.NoError(t, err) + assert.NoError(t, fakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName())) }(gvr, u) } } @@ -681,9 +672,9 @@ func TestStatusWaitMultipleNamespaces(t *testing.T) { for _, expectedErrStr := range tt.expectErrStrs { require.ErrorContains(t, err, expectedErrStr) } - return + } else { + assert.NoError(t, err) } - assert.NoError(t, err) }) } } @@ -839,8 +830,7 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := baseFakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, baseFakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } if strings.Contains(tt.name, "delet") { @@ -850,8 +840,7 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { gvr := getGVR(t, fakeMapper, u) go func(gvr schema.GroupVersionResource, u *unstructured.Unstructured) { time.Sleep(timeUntilDelete) - err := baseFakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName()) - assert.NoError(t, err) + assert.NoError(t, baseFakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName())) }(gvr, u) } } @@ -863,10 +852,10 @@ func TestStatusWaitRestrictedRBAC(t *testing.T) { for _, expectedErr := range tt.expectErrs { require.ErrorContains(t, err, expectedErr.Error()) } - return + } else { + require.NoError(t, err) + assert.False(t, restrictedConfig.clusterScopedListAttempted) } - require.NoError(t, err) - assert.False(t, restrictedConfig.clusterScopedListAttempted) }) } } @@ -947,8 +936,7 @@ func TestStatusWaitMixedResources(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := baseFakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, baseFakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } if strings.Contains(tt.name, "delet") { @@ -958,8 +946,7 @@ func TestStatusWaitMixedResources(t *testing.T) { gvr := getGVR(t, fakeMapper, u) go func(gvr schema.GroupVersionResource, u *unstructured.Unstructured) { time.Sleep(timeUntilDelete) - err := baseFakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName()) - assert.NoError(t, err) + assert.NoError(t, baseFakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName())) }(gvr, u) } } @@ -971,10 +958,10 @@ func TestStatusWaitMixedResources(t *testing.T) { for _, expectedErr := range tt.expectErrs { require.ErrorContains(t, err, expectedErr.Error()) } - return + } else { + require.NoError(t, err) + assert.False(t, restrictedConfig.clusterScopedListAttempted) } - require.NoError(t, err) - assert.False(t, restrictedConfig.clusterScopedListAttempted) }) } } @@ -1067,8 +1054,7 @@ func TestStatusWaitWithCustomReaders(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.Wait(resourceList, time.Second*3) @@ -1077,9 +1063,9 @@ func TestStatusWaitWithCustomReaders(t *testing.T) { for _, expectedErrStr := range tt.expectErrStrs { require.ErrorContains(t, err, expectedErrStr) } - return + } else { + assert.NoError(t, err) } - assert.NoError(t, err) }) } } @@ -1139,16 +1125,15 @@ func TestStatusWaitWithJobsAndCustomReaders(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.WaitWithJobs(resourceList, time.Second*3) if tt.expectErrs != nil { assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) - return + } else { + assert.NoError(t, err) } - assert.NoError(t, err) }) } } @@ -1238,8 +1223,7 @@ func TestStatusWaitWithFailedResources(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := tt.testFunc(&sw, resourceList, time.Second*3) @@ -1248,9 +1232,9 @@ func TestStatusWaitWithFailedResources(t *testing.T) { for _, expectedErrStr := range tt.expectErrStrs { require.ErrorContains(t, err, expectedErrStr) } - return + } else { + assert.NoError(t, err) } - assert.NoError(t, err) }) } } @@ -1343,14 +1327,12 @@ func TestMethodSpecificContextCancellation(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := sw.WatchUntilReady(resourceList, time.Second*3) // Should fail due to cancelled method context - assert.ErrorContains(t, err, "context canceled") + assert.ErrorContains(t, sw.WatchUntilReady(resourceList, time.Second*3), "context canceled") }) t.Run("Wait uses method-specific context", func(t *testing.T) { @@ -1376,14 +1358,12 @@ func TestMethodSpecificContextCancellation(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := sw.Wait(resourceList, time.Second*3) // Should fail due to cancelled method context - assert.ErrorContains(t, err, "context canceled") + assert.ErrorContains(t, sw.Wait(resourceList, time.Second*3), "context canceled") }) t.Run("WaitWithJobs uses method-specific context", func(t *testing.T) { @@ -1409,14 +1389,12 @@ func TestMethodSpecificContextCancellation(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := sw.WaitWithJobs(resourceList, time.Second*3) // Should fail due to cancelled method context - assert.ErrorContains(t, err, "context canceled") + assert.ErrorContains(t, sw.WaitWithJobs(resourceList, time.Second*3), "context canceled") }) t.Run("WaitForDelete uses method-specific context", func(t *testing.T) { @@ -1442,14 +1420,12 @@ func TestMethodSpecificContextCancellation(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := sw.WaitForDelete(resourceList, time.Second*3) // Should fail due to cancelled method context - assert.ErrorContains(t, err, "context canceled") + assert.ErrorContains(t, sw.WaitForDelete(resourceList, time.Second*3), "context canceled") }) } @@ -1479,14 +1455,12 @@ func TestMethodContextFallbackToGeneralContext(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := sw.WatchUntilReady(resourceList, time.Second*3) // Should fail due to cancelled general context - assert.ErrorContains(t, err, "context canceled") + assert.ErrorContains(t, sw.WatchUntilReady(resourceList, time.Second*3), "context canceled") }) t.Run("Wait falls back to general context when method context is nil", func(t *testing.T) { @@ -1512,14 +1486,12 @@ func TestMethodContextFallbackToGeneralContext(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := sw.Wait(resourceList, time.Second*3) // Should fail due to cancelled general context - assert.ErrorContains(t, err, "context canceled") + assert.ErrorContains(t, sw.Wait(resourceList, time.Second*3), "context canceled") }) t.Run("WaitWithJobs falls back to general context when method context is nil", func(t *testing.T) { @@ -1545,14 +1517,12 @@ func TestMethodContextFallbackToGeneralContext(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := sw.WaitWithJobs(resourceList, time.Second*3) // Should fail due to cancelled general context - assert.ErrorContains(t, err, "context canceled") + assert.ErrorContains(t, sw.WaitWithJobs(resourceList, time.Second*3), "context canceled") }) t.Run("WaitForDelete falls back to general context when method context is nil", func(t *testing.T) { @@ -1578,14 +1548,12 @@ func TestMethodContextFallbackToGeneralContext(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := sw.WaitForDelete(resourceList, time.Second*3) // Should fail due to cancelled general context - assert.ErrorContains(t, err, "context canceled") + assert.ErrorContains(t, sw.WaitForDelete(resourceList, time.Second*3), "context canceled") }) } @@ -1615,14 +1583,12 @@ func TestMethodContextOverridesGeneralContext(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := sw.WatchUntilReady(resourceList, time.Second*3) // Should succeed because method context is used and it's not cancelled - assert.NoError(t, err) + assert.NoError(t, sw.WatchUntilReady(resourceList, time.Second*3)) }) t.Run("method-specific context overrides general context for Wait", func(t *testing.T) { @@ -1648,14 +1614,12 @@ func TestMethodContextOverridesGeneralContext(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := sw.Wait(resourceList, time.Second*3) // Should succeed because method context is used and it's not cancelled - assert.NoError(t, err) + assert.NoError(t, sw.Wait(resourceList, time.Second*3)) }) t.Run("method-specific context overrides general context for WaitWithJobs", func(t *testing.T) { @@ -1681,14 +1645,12 @@ func TestMethodContextOverridesGeneralContext(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := sw.WaitWithJobs(resourceList, time.Second*3) // Should succeed because method context is used and it's not cancelled - assert.NoError(t, err) + assert.NoError(t, sw.WaitWithJobs(resourceList, time.Second*3)) }) t.Run("method-specific context overrides general context for WaitForDelete", func(t *testing.T) { @@ -1717,9 +1679,8 @@ func TestMethodContextOverridesGeneralContext(t *testing.T) { // immediately cancelled and the call would return a context error. objs := getRuntimeObjFromManifests(t, []string{podCurrentManifest}) resourceList := getResourceListFromRuntimeObjs(t, c, objs) - err := sw.WaitForDelete(resourceList, time.Second) // Should succeed because method context is used and it's not cancelled - assert.NoError(t, err) + assert.NoError(t, sw.WaitForDelete(resourceList, time.Second)) }) } @@ -1801,8 +1762,7 @@ func TestWatchUntilReadyWithCustomReaders(t *testing.T) { for _, obj := range objs { u := obj.(*unstructured.Unstructured) gvr := getGVR(t, fakeMapper, u) - err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace()) - require.NoError(t, err) + require.NoError(t, fakeClient.Tracker().Create(gvr, u, u.GetNamespace())) } resourceList := getResourceListFromRuntimeObjs(t, c, objs) err := statusWaiter.WatchUntilReady(resourceList, time.Second*3) @@ -1811,9 +1771,9 @@ func TestWatchUntilReadyWithCustomReaders(t *testing.T) { for _, expectedErrStr := range tt.expectErrStrs { require.ErrorContains(t, err, expectedErrStr) } - return + } else { + assert.NoError(t, err) } - assert.NoError(t, err) }) } } diff --git a/pkg/storage/driver/cfgmaps_test.go b/pkg/storage/driver/cfgmaps_test.go index 947ebff71..e36aecce8 100644 --- a/pkg/storage/driver/cfgmaps_test.go +++ b/pkg/storage/driver/cfgmaps_test.go @@ -16,10 +16,11 @@ package driver import ( "encoding/base64" "encoding/json" - "errors" "reflect" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" "helm.sh/helm/v4/pkg/release" @@ -29,9 +30,7 @@ import ( func TestConfigMapName(t *testing.T) { c := newTestFixtureCfgMaps(t) - if c.Name() != ConfigMapsDriverName { - t.Errorf("Expected name to be %q, got %q", ConfigMapsDriverName, c.Name()) - } + assert.Equal(t, ConfigMapsDriverName, c.Name(), "Expected name to be %q, got %q", ConfigMapsDriverName, c.Name()) } func TestConfigMapGet(t *testing.T) { @@ -45,13 +44,9 @@ func TestConfigMapGet(t *testing.T) { // get release with key got, err := cfgmaps.Get(key) - if err != nil { - t.Fatalf("Failed to get release: %s", err) - } + require.NoError(t, err, "Failed to get release") // compare fetched release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } + assert.Truef(t, reflect.DeepEqual(rel, got), "Expected {%v}, got {%v}", rel, got) } func TestUncompressedConfigMapGet(t *testing.T) { @@ -63,13 +58,9 @@ func TestUncompressedConfigMapGet(t *testing.T) { // Create a test fixture which contains an uncompressed release cfgmap, err := newConfigMapsObject(key, rel, nil) - if err != nil { - t.Fatalf("Failed to create configmap: %s", err) - } + require.NoError(t, err, "Failed to create configmap") b, err := json.Marshal(rel) - if err != nil { - t.Fatalf("Failed to marshal release: %s", err) - } + require.NoError(t, err, "Failed to marshal release") cfgmap.Data["release"] = base64.StdEncoding.EncodeToString(b) var mock MockConfigMapsInterface mock.objects = map[string]*v1.ConfigMap{key: cfgmap} @@ -77,13 +68,9 @@ func TestUncompressedConfigMapGet(t *testing.T) { // get release with key got, err := cfgmaps.Get(key) - if err != nil { - t.Fatalf("Failed to get release: %s", err) - } + require.NoError(t, err, "Failed to get release") // compare fetched release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } + assert.Truef(t, reflect.DeepEqual(rel, got), "Expected {%v}, got {%v}", rel, got) } func convertReleaserToV1(t *testing.T, rel release.Releaser) *rspb.Release { @@ -117,12 +104,8 @@ func TestConfigMapList(t *testing.T) { return rls.Info.Status == common.StatusUninstalled }) // check - if err != nil { - t.Errorf("Failed to list deleted: %s", err) - } - if len(del) != 2 { - t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del) - } + require.NoError(t, err, "Failed to list deleted") + assert.Len(t, del, 2, "Expected 2 deleted, got %d:\n%v\n", len(del), del) // list all deployed releases dpl, err := cfgmaps.List(func(rel release.Releaser) bool { @@ -130,12 +113,8 @@ func TestConfigMapList(t *testing.T) { return rls.Info.Status == common.StatusDeployed }) // check - if err != nil { - t.Errorf("Failed to list deployed: %s", err) - } - if len(dpl) != 2 { - t.Errorf("Expected 2 deployed, got %d", len(dpl)) - } + require.NoError(t, err, "Failed to list deployed") + assert.Len(t, dpl, 2, "Expected 2 deployed, got %d", len(dpl)) // list all superseded releases ssd, err := cfgmaps.List(func(rel release.Releaser) bool { @@ -143,22 +122,14 @@ func TestConfigMapList(t *testing.T) { return rls.Info.Status == common.StatusSuperseded }) // check - if err != nil { - t.Errorf("Failed to list superseded: %s", err) - } - if len(ssd) != 2 { - t.Errorf("Expected 2 superseded, got %d", len(ssd)) - } + require.NoError(t, err, "Failed to list superseded") + assert.Len(t, ssd, 2, "Expected 2 superseded, got %d", len(ssd)) // Check if release having both system and custom labels, this is needed to ensure that selector filtering would work. rls := convertReleaserToV1(t, ssd[0]) _, ok := rls.Labels["name"] - if !ok { - t.Fatalf("Expected 'name' label in results, actual %v", rls.Labels) - } + require.True(t, ok, "Expected 'name' label in results, actual %v", rls.Labels) _, ok = rls.Labels["key1"] - if !ok { - t.Fatalf("Expected 'key1' label in results, actual %v", rls.Labels) - } + require.True(t, ok, "Expected 'key1' label in results, actual %v", rls.Labels) } func TestConfigMapQuery(t *testing.T) { @@ -172,17 +143,11 @@ func TestConfigMapQuery(t *testing.T) { }...) rls, err := cfgmaps.Query(map[string]string{"status": "deployed"}) - if err != nil { - t.Errorf("Failed to query: %s", err) - } - if len(rls) != 2 { - t.Errorf("Expected 2 results, got %d", len(rls)) - } + require.NoError(t, err, "Failed to query") + assert.Len(t, rls, 2, "Expected 2 results, got %d", len(rls)) _, err = cfgmaps.Query(map[string]string{"name": "notExist"}) - if !errors.Is(err, ErrReleaseNotFound) { - t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) - } + assert.ErrorIs(t, err, ErrReleaseNotFound) } func TestConfigMapCreate(t *testing.T) { @@ -195,20 +160,14 @@ func TestConfigMapCreate(t *testing.T) { rel := releaseStub(name, vers, namespace, common.StatusDeployed) // store the release in a configmap - if err := cfgmaps.Create(key, rel); err != nil { - t.Fatalf("Failed to create release with key %q: %s", key, err) - } + require.NoErrorf(t, cfgmaps.Create(key, rel), "Failed to create release with key %q", key) // get the release back got, err := cfgmaps.Get(key) - if err != nil { - t.Fatalf("Failed to get release with key %q: %s", key, err) - } + require.NoError(t, err, "Failed to get release with key %q", key) // compare created release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } + assert.Truef(t, reflect.DeepEqual(rel, got), "Expected {%v}, got {%v}", rel, got) } func TestConfigMapUpdate(t *testing.T) { @@ -224,21 +183,15 @@ func TestConfigMapUpdate(t *testing.T) { rel.Info.Status = common.StatusSuperseded // perform the update - if err := cfgmaps.Update(key, rel); err != nil { - t.Fatalf("Failed to update release: %s", err) - } + require.NoErrorf(t, cfgmaps.Update(key, rel), "Failed to update release") // fetch the updated release goti, err := cfgmaps.Get(key) - if err != nil { - t.Fatalf("Failed to get release with key %q: %s", key, err) - } + require.NoError(t, err, "Failed to get release with key %q", key) got := convertReleaserToV1(t, goti) // check release has actually been updated by comparing modified fields - if rel.Info.Status != got.Info.Status { - t.Errorf("Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String()) - } + assert.Equal(t, got.Info.Status, rel.Info.Status, "Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String()) } func TestConfigMapDelete(t *testing.T) { @@ -252,20 +205,12 @@ func TestConfigMapDelete(t *testing.T) { // perform the delete on a non-existent release _, err := cfgmaps.Delete("nonexistent") - if !errors.Is(err, ErrReleaseNotFound) { - t.Fatalf("Expected ErrReleaseNotFound: got {%v}", err) - } + require.ErrorIs(t, err, ErrReleaseNotFound) // perform the delete rls, err := cfgmaps.Delete(key) - if err != nil { - t.Fatalf("Failed to delete release with key %q: %s", key, err) - } - if !reflect.DeepEqual(rel, rls) { - t.Errorf("Expected {%v}, got {%v}", rel, rls) - } + require.NoError(t, err, "Failed to delete release with key %q", key) + assert.Truef(t, reflect.DeepEqual(rel, rls), "Expected {%v}, got {%v}", rel, rls) _, err = cfgmaps.Get(key) - if !errors.Is(err, ErrReleaseNotFound) { - t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) - } + assert.ErrorIs(t, err, ErrReleaseNotFound) } From 13d1edc5f346e136b249d4c8232e83689f07c3b4 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:55 +0200 Subject: [PATCH 465/571] chore(pkg): refactor: convert tests to testify assert/require part 9 refactor: convert tests to testify assert/require in pkg/chart/common/util Signed-off-by: Matthieu MOREL --- pkg/chart/common/util/coalesce_test.go | 269 +++++++---------------- pkg/chart/common/util/jsonschema_test.go | 132 +++-------- pkg/cmd/completion_test.go | 6 +- 3 files changed, 116 insertions(+), 291 deletions(-) diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index d7f46a1da..6ef092365 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -141,9 +141,7 @@ func TestCoalesceValues(t *testing.T) { ) vals, err := common.ReadValues(testCoalesceValuesYaml) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // taking a copy of the values before passing it // to CoalesceValues as argument, so that we can @@ -152,9 +150,7 @@ func TestCoalesceValues(t *testing.T) { maps.Copy(valsCopy, vals) v, err := CoalesceValues(c, vals) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) j, _ := json.MarshalIndent(v, "", " ") t.Logf("Coalesced Values: %s", string(j)) @@ -211,32 +207,26 @@ func TestCoalesceValues(t *testing.T) { nullKeys := []string{"bottom", "right", "left", "front"} for _, nullKey := range nullKeys { - if _, ok := v[nullKey]; ok { - t.Errorf("Expected key %q to be removed, still present", nullKey) - } + _, ok := v[nullKey] + assert.Falsef(t, ok, "Expected key %q to be removed, still present", nullKey) } - if _, ok := v["nested"].(map[string]any)["boat"]; ok { - t.Error("Expected nested boat key to be removed, still present") - } + _, ok := v["nested"].(map[string]any)["boat"] + assert.False(t, ok, "Expected nested boat key to be removed, still present") subchart := v["pequod"].(map[string]any) - if _, ok := subchart["boat"]; ok { - t.Error("Expected subchart boat key to be removed, still present") - } + _, ok = subchart["boat"] + assert.False(t, ok, "Expected subchart boat key to be removed, still present") subsubchart := subchart["ahab"].(map[string]any) - if _, ok := subsubchart["boat"]; ok { - t.Error("Expected sub-subchart ahab boat key to be removed, still present") - } + _, ok = subsubchart["boat"] + assert.False(t, ok, "Expected sub-subchart ahab boat key to be removed, still present") - if _, ok := subsubchart["nested"].(map[string]any)["boat"]; ok { - t.Error("Expected sub-subchart nested boat key to be removed, still present") - } + _, ok = subsubchart["nested"].(map[string]any)["boat"] + assert.False(t, ok, "Expected sub-subchart nested boat key to be removed, still present") - if _, ok := subsubchart["object"]; ok { - t.Error("Expected sub-subchart object map to be removed, still present") - } + _, ok = subsubchart["object"] + assert.False(t, ok, "Expected sub-subchart object map to be removed, still present") // CoalesceValues should not mutate the passed arguments is.Equal(valsCopy, vals) @@ -306,9 +296,7 @@ func TestMergeValues(t *testing.T) { ) vals, err := common.ReadValues(testCoalesceValuesYaml) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // taking a copy of the values before passing it // to MergeValues as argument, so that we can @@ -317,9 +305,7 @@ func TestMergeValues(t *testing.T) { maps.Copy(valsCopy, vals) v, err := MergeValues(c, vals) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) j, _ := json.MarshalIndent(v, "", " ") t.Logf("Coalesced Values: %s", string(j)) @@ -377,25 +363,20 @@ func TestMergeValues(t *testing.T) { // removed. nullKeys := []string{"bottom", "right", "left", "front"} for _, nullKey := range nullKeys { - if vv, ok := v[nullKey]; !ok { - t.Errorf("Expected key %q to be present but it was removed", nullKey) - } else if vv != nil { - t.Errorf("Expected key %q to be null but it has a value of %v", nullKey, vv) - } + vv, ok := v[nullKey] + assert.Truef(t, ok, "Expected key %q to be present but it was removed", nullKey) + assert.Nilf(t, vv, "Expected key %q to be null but it has a value of %v", nullKey, vv) } - if _, ok := v["nested"].(map[string]any)["boat"]; !ok { - t.Error("Expected nested boat key to be present but it was removed") - } + _, ok := v["nested"].(map[string]any)["boat"] + assert.True(t, ok, "Expected nested boat key to be present but it was removed") subchart := v["pequod"].(map[string]any)["ahab"].(map[string]any) - if _, ok := subchart["boat"]; !ok { - t.Error("Expected subchart boat key to be present but it was removed") - } + _, ok = subchart["boat"] + assert.True(t, ok, "Expected subchart boat key to be present but it was removed") - if _, ok := subchart["nested"].(map[string]any)["bar"]; !ok { - t.Error("Expected subchart nested bar key to be present but it was removed") - } + _, ok = subchart["nested"].(map[string]any)["bar"] + assert.True(t, ok, "Expected subchart nested bar key to be present but it was removed") // CoalesceValues should not mutate the passed arguments is.Equal(valsCopy, vals) @@ -433,47 +414,27 @@ func TestCoalesceTables(t *testing.T) { // otherwise the values are coalesced. CoalesceTables(dst, src) - if dst["name"] != "Ishmael" { - t.Errorf("Unexpected name: %s", dst["name"]) - } - if dst["occupation"] != "whaler" { - t.Errorf("Unexpected occupation: %s", dst["occupation"]) - } + assert.Equal(t, "Ishmael", dst["name"], "Unexpected name: %s", dst["name"]) + assert.Equal(t, "whaler", dst["occupation"], "Unexpected occupation: %s", dst["occupation"]) addr, ok := dst["address"].(map[string]any) - if !ok { - t.Fatal("Address went away.") - } - - if addr["street"].(string) != "123 Spouter Inn Ct." { - t.Errorf("Unexpected address: %v", addr["street"]) - } + require.True(t, ok, "Address went away.") + assert.Equal(t, "123 Spouter Inn Ct.", addr["street"].(string), "Unexpected address: %v", addr["street"]) + assert.Equal(t, "Nantucket", addr["city"].(string), "Unexpected city: %v", addr["city"]) + assert.Equal(t, "MA", addr["state"].(string), "Unexpected state: %v", addr["state"]) - if addr["city"].(string) != "Nantucket" { - t.Errorf("Unexpected city: %v", addr["city"]) - } + _, ok = addr["country"] + assert.False(t, ok, "The country is not left out.") - if addr["state"].(string) != "MA" { - t.Errorf("Unexpected state: %v", addr["state"]) - } + det, ok := dst["details"].(map[string]any) + require.Truef(t, ok, "Details is the wrong type: %v", dst["details"]) - if _, ok = addr["country"]; ok { - t.Error("The country is not left out.") - } + _, ok = det["friends"] + assert.True(t, ok, "Could not find your friends. Maybe you don't have any. :-(") + assert.Equal(t, "pequod", dst["boat"].(string), "Expected boat string, got %v", dst["boat"]) - if det, ok := dst["details"].(map[string]any); !ok { - t.Fatalf("Details is the wrong type: %v", dst["details"]) - } else if _, ok := det["friends"]; !ok { - t.Error("Could not find your friends. Maybe you don't have any. :-(") - } - - if dst["boat"].(string) != "pequod" { - t.Errorf("Expected boat string, got %v", dst["boat"]) - } - - if _, ok = dst["hole"]; ok { - t.Error("The hole still exists.") - } + _, ok = dst["hole"] + assert.False(t, ok, "The hole still exists.") dst2 := map[string]any{ "name": "Ishmael", @@ -493,40 +454,21 @@ func TestCoalesceTables(t *testing.T) { // this happens when the --reuse-values flag is set but the chart has no modifications yet CoalesceTables(dst2, nil) - if dst2["name"] != "Ishmael" { - t.Errorf("Unexpected name: %s", dst2["name"]) - } + assert.Equal(t, "Ishmael", dst2["name"], "Unexpected name: %s", dst2["name"]) addr2, ok := dst2["address"].(map[string]any) - if !ok { - t.Fatal("Address went away.") - } - - if addr2["street"].(string) != "123 Spouter Inn Ct." { - t.Errorf("Unexpected address: %v", addr2["street"]) - } - - if addr2["city"].(string) != "Nantucket" { - t.Errorf("Unexpected city: %v", addr2["city"]) - } - - if addr2["country"].(string) != "US" { - t.Errorf("Unexpected Country: %v", addr2["country"]) - } - - if det2, ok := dst2["details"].(map[string]any); !ok { - t.Fatalf("Details is the wrong type: %v", dst2["details"]) - } else if _, ok := det2["friends"]; !ok { - t.Error("Could not find your friends. Maybe you don't have any. :-(") - } - - if dst2["boat"].(string) != "pequod" { - t.Errorf("Expected boat string, got %v", dst2["boat"]) - } - - if dst2["hole"].(string) != "black" { - t.Errorf("Expected hole string, got %v", dst2["boat"]) - } + require.True(t, ok, "Address went away.") + assert.Equal(t, "123 Spouter Inn Ct.", addr2["street"].(string), "Unexpected address: %v", addr2["street"]) + assert.Equal(t, "Nantucket", addr2["city"].(string), "Unexpected city: %v", addr2["city"]) + assert.Equal(t, "US", addr2["country"].(string), "Unexpected Country: %v", addr2["country"]) + + det2, ok := dst2["details"].(map[string]any) + require.Truef(t, ok, "Details is the wrong type: %v", dst2["details"]) + + _, ok = det2["friends"] + assert.True(t, ok, "Could not find your friends. Maybe you don't have any. :-(") + assert.Equal(t, "pequod", dst2["boat"].(string), "Expected boat string, got %v", dst2["boat"]) + assert.Equal(t, "black", dst2["hole"].(string), "Expected hole string, got %v", dst2["boat"]) } func TestMergeTables(t *testing.T) { @@ -561,51 +503,31 @@ func TestMergeTables(t *testing.T) { // otherwise the values are coalesced. MergeTables(dst, src) - if dst["name"] != "Ishmael" { - t.Errorf("Unexpected name: %s", dst["name"]) - } - if dst["occupation"] != "whaler" { - t.Errorf("Unexpected occupation: %s", dst["occupation"]) - } + assert.Equal(t, "Ishmael", dst["name"], "Unexpected name: %s", dst["name"]) + assert.Equal(t, "whaler", dst["occupation"], "Unexpected occupation: %s", dst["occupation"]) addr, ok := dst["address"].(map[string]any) - if !ok { - t.Fatal("Address went away.") - } - - if addr["street"].(string) != "123 Spouter Inn Ct." { - t.Errorf("Unexpected address: %v", addr["street"]) - } - - if addr["city"].(string) != "Nantucket" { - t.Errorf("Unexpected city: %v", addr["city"]) - } - - if addr["state"].(string) != "MA" { - t.Errorf("Unexpected state: %v", addr["state"]) - } + require.True(t, ok, "Address went away.") + assert.Equal(t, "123 Spouter Inn Ct.", addr["street"].(string), "Unexpected address: %v", addr["street"]) + assert.Equal(t, "Nantucket", addr["city"].(string), "Unexpected city: %v", addr["city"]) + assert.Equal(t, "MA", addr["state"].(string), "Unexpected state: %v", addr["state"]) // This is one test that is different from CoalesceTables. Because country // is a nil value and it's not removed it's still present. - if _, ok = addr["country"]; !ok { - t.Error("The country is left out.") - } + _, ok = addr["country"] + assert.True(t, ok, "The country is left out.") - if det, ok := dst["details"].(map[string]any); !ok { - t.Fatalf("Details is the wrong type: %v", dst["details"]) - } else if _, ok := det["friends"]; !ok { - t.Error("Could not find your friends. Maybe you don't have any. :-(") - } + det, ok := dst["details"].(map[string]any) + require.Truef(t, ok, "Details is the wrong type: %v", dst["details"]) - if dst["boat"].(string) != "pequod" { - t.Errorf("Expected boat string, got %v", dst["boat"]) - } + _, ok = det["friends"] + assert.True(t, ok, "Could not find your friends. Maybe you don't have any. :-(") + assert.Equal(t, "pequod", dst["boat"].(string), "Expected boat string, got %v", dst["boat"]) // This is one test that is different from CoalesceTables. Because hole // is a nil value and it's not removed it's still present. - if _, ok = dst["hole"]; !ok { - t.Error("The hole no longer exists.") - } + _, ok = dst["hole"] + assert.True(t, ok, "The hole no longer exists.") dst2 := map[string]any{ "name": "Ishmael", @@ -626,44 +548,22 @@ func TestMergeTables(t *testing.T) { // this happens when the --reuse-values flag is set but the chart has no modifications yet MergeTables(dst2, nil) - if dst2["name"] != "Ishmael" { - t.Errorf("Unexpected name: %s", dst2["name"]) - } + assert.Equal(t, "Ishmael", dst2["name"], "Unexpected name: %s", dst2["name"]) addr2, ok := dst2["address"].(map[string]any) - if !ok { - t.Fatal("Address went away.") - } - - if addr2["street"].(string) != "123 Spouter Inn Ct." { - t.Errorf("Unexpected address: %v", addr2["street"]) - } - - if addr2["city"].(string) != "Nantucket" { - t.Errorf("Unexpected city: %v", addr2["city"]) - } - - if addr2["country"].(string) != "US" { - t.Errorf("Unexpected Country: %v", addr2["country"]) - } - - if det2, ok := dst2["details"].(map[string]any); !ok { - t.Fatalf("Details is the wrong type: %v", dst2["details"]) - } else if _, ok := det2["friends"]; !ok { - t.Error("Could not find your friends. Maybe you don't have any. :-(") - } - - if dst2["boat"].(string) != "pequod" { - t.Errorf("Expected boat string, got %v", dst2["boat"]) - } - - if dst2["hole"].(string) != "black" { - t.Errorf("Expected hole string, got %v", dst2["boat"]) - } - - if dst2["nilval"] != nil { - t.Error("Expected nilvalue to have nil value but it does not") - } + require.True(t, ok, "Address went away.") + assert.Equal(t, "123 Spouter Inn Ct.", addr2["street"].(string), "Unexpected address: %v", addr2["street"]) + assert.Equal(t, "Nantucket", addr2["city"].(string), "Unexpected city: %v", addr2["city"]) + assert.Equal(t, "US", addr2["country"].(string), "Unexpected Country: %v", addr2["country"]) + + det2, ok := dst2["details"].(map[string]any) + require.Truef(t, ok, "Details is the wrong type: %v", dst2["details"]) + + _, ok = det2["friends"] + assert.True(t, ok, "Could not find your friends. Maybe you don't have any. :-(") + assert.Equal(t, "pequod", dst2["boat"].(string), "Expected boat string, got %v", dst2["boat"]) + assert.Equal(t, "black", dst2["hole"].(string), "Expected hole string, got %v", dst2["hole"]) + assert.Nil(t, dst2["nilval"], "Expected nilvalue to have nil value but it does not") } func TestCoalesceValuesWarnings(t *testing.T) { @@ -716,9 +616,7 @@ func TestCoalesceValuesWarnings(t *testing.T) { } _, err := coalesce(printf, c, vals, "", false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) t.Logf("vals: %v", vals) assert.Contains(t, warnings, "warning: skipped value for level1.level2.level3.boat: Not a table.") @@ -926,7 +824,6 @@ func TestCoalesceValuesSubchartNilCleanedWhenUserPartiallyOverrides(t *testing.T keyMapping, ok := childVals["keyMapping"].(map[string]any) is.True(ok, "keyMapping should be a map") - is.Equal("sha256", keyMapping["format"], "User override should be preserved") _, ok = keyMapping["password"] diff --git a/pkg/chart/common/util/jsonschema_test.go b/pkg/chart/common/util/jsonschema_test.go index 838d152a1..90fdc9b11 100644 --- a/pkg/chart/common/util/jsonschema_test.go +++ b/pkg/chart/common/util/jsonschema_test.go @@ -20,75 +20,47 @@ import ( "net/http" "net/http/httptest" "os" - "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" ) func TestValidateAgainstSingleSchema(t *testing.T) { values, err := common.ReadValuesFile("./testdata/test-values.yaml") - if err != nil { - t.Fatalf("Error reading YAML file: %s", err) - } - schema, err := os.ReadFile("./testdata/test-values.schema.json") - if err != nil { - t.Fatalf("Error reading YAML file: %s", err) - } + require.NoError(t, err, "Error reading YAML file") - if err := ValidateAgainstSingleSchema(values, schema); err != nil { - t.Errorf("Error validating Values against Schema: %s", err) - } + schema, err := os.ReadFile("./testdata/test-values.schema.json") + require.NoError(t, err, "Error reading YAML file") + assert.NoErrorf(t, ValidateAgainstSingleSchema(values, schema), "Error validating Values against Schema") } func TestValidateAgainstInvalidSingleSchema(t *testing.T) { values, err := common.ReadValuesFile("./testdata/test-values.yaml") - if err != nil { - t.Fatalf("Error reading YAML file: %s", err) - } - schema, err := os.ReadFile("./testdata/test-values-invalid.schema.json") - if err != nil { - t.Fatalf("Error reading YAML file: %s", err) - } + require.NoError(t, err, "Error reading YAML file") - var errString string - if err := ValidateAgainstSingleSchema(values, schema); err == nil { - t.Fatal("Expected an error, but got nil") - } else { - errString = err.Error() - } + schema, err := os.ReadFile("./testdata/test-values-invalid.schema.json") + require.NoError(t, err, "Error reading YAML file") expectedErrString := `"file:///values.schema.json#" is not valid against metaschema: jsonschema validation failed with 'https://json-schema.org/draft/2020-12/schema#' - at '': got number, want boolean or object` - if errString != expectedErrString { - t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) - } + assert.EqualError(t, ValidateAgainstSingleSchema(values, schema), expectedErrString) } func TestValidateAgainstSingleSchemaNegative(t *testing.T) { values, err := common.ReadValuesFile("./testdata/test-values-negative.yaml") - if err != nil { - t.Fatalf("Error reading YAML file: %s", err) - } - schema, err := os.ReadFile("./testdata/test-values.schema.json") - if err != nil { - t.Fatalf("Error reading JSON file: %s", err) - } + require.NoError(t, err, "Error reading YAML file") - var errString string - if err := ValidateAgainstSingleSchema(values, schema); err == nil { - t.Fatal("Expected an error, but got nil") - } else { - errString = err.Error() - } + schema, err := os.ReadFile("./testdata/test-values.schema.json") + require.NoError(t, err, "Error reading JSON file") expectedErrString := `- at '': missing property 'employmentInfo' - at '/age': minimum: got -5, want 0 ` - if errString != expectedErrString { - t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) - } + assert.EqualError(t, ValidateAgainstSingleSchema(values, schema), expectedErrString) } const subchartSchema = `{ @@ -145,9 +117,7 @@ func TestValidateAgainstSchema(t *testing.T) { }, } - if err := ValidateAgainstSchema(chrt, vals); err != nil { - t.Errorf("Error validating Values against Schema: %s", err) - } + assert.NoErrorf(t, ValidateAgainstSchema(chrt, vals), "Error validating Values against Schema") } func TestValidateAgainstSchemaNegative(t *testing.T) { @@ -170,19 +140,10 @@ func TestValidateAgainstSchemaNegative(t *testing.T) { "subchart": map[string]any{}, } - var errString string - if err := ValidateAgainstSchema(chrt, vals); err == nil { - t.Fatal("Expected an error, but got nil") - } else { - errString = err.Error() - } - expectedErrString := `subchart: - at '': missing property 'age' ` - if errString != expectedErrString { - t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) - } + assert.EqualError(t, ValidateAgainstSchema(chrt, vals), expectedErrString) } func TestValidateAgainstSchema2020(t *testing.T) { @@ -207,9 +168,7 @@ func TestValidateAgainstSchema2020(t *testing.T) { }, } - if err := ValidateAgainstSchema(chrt, vals); err != nil { - t.Errorf("Error validating Values against Schema: %s", err) - } + assert.NoErrorf(t, ValidateAgainstSchema(chrt, vals), "Error validating Values against Schema") } func TestValidateAgainstSchema2020Negative(t *testing.T) { @@ -234,20 +193,11 @@ func TestValidateAgainstSchema2020Negative(t *testing.T) { }, } - var errString string - if err := ValidateAgainstSchema(chrt, vals); err == nil { - t.Fatal("Expected an error, but got nil") - } else { - errString = err.Error() - } - expectedErrString := `subchart: - at '/data': no items match contains schema - at '/data/0': got number, want string ` - if errString != expectedErrString { - t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) - } + assert.EqualError(t, ValidateAgainstSchema(chrt, vals), expectedErrString) } func TestHTTPURLLoader_Load(t *testing.T) { @@ -262,12 +212,8 @@ func TestHTTPURLLoader_Load(t *testing.T) { loader := newHTTPURLLoader() result, err := loader.Load(server.URL) - if err != nil { - t.Fatalf("Expected no error, got: %v", err) - } - if result == nil { - t.Fatal("Expected result to be non-nil") - } + require.NoError(t, err, "Expected no error, got") + require.NotNil(t, result, "Expected result to be non-nil") }) t.Run("HTTP error status", func(t *testing.T) { @@ -278,12 +224,8 @@ func TestHTTPURLLoader_Load(t *testing.T) { loader := newHTTPURLLoader() _, err := loader.Load(server.URL) - if err == nil { - t.Fatal("Expected error for HTTP 404") - } - if !strings.Contains(err.Error(), "404") { - t.Errorf("Expected error message to contain '404', got: %v", err) - } + require.Error(t, err, "Expected error for HTTP 404") + assert.ErrorContains(t, err, "404", "Expected error message to contain '404'") }) } @@ -295,9 +237,7 @@ func TestValidateAgainstSingleSchema_UnresolvedURN_Ignored(t *testing.T) { "$ref": "urn:example:helm:schemas:v1:helm-schema-validation-conditions:v1/helmSchemaValidation-true" }`) vals := map[string]any{"any": "value"} - if err := ValidateAgainstSingleSchema(vals, schema); err != nil { - t.Fatalf("expected no error when URN unresolved is ignored, got: %v", err) - } + require.NoErrorf(t, ValidateAgainstSingleSchema(vals, schema), "expected no error when URN unresolved is ignored, got") } // Non-regression tests for https://github.com/helm/helm/issues/31202 @@ -323,14 +263,10 @@ func TestValidateAgainstSchema_MissingSubchartValues_NoPanic(t *testing.T) { } defer func() { - if r := recover(); r != nil { - t.Fatalf("ValidateAgainstSchema panicked (missing subchart values): %v", r) - } + require.Nilf(t, recover(), "ValidateAgainstSchema panicked (missing subchart values)") }() - if err := ValidateAgainstSchema(chrt, vals); err != nil { - t.Fatalf("expected no error when subchart values are missing, got: %v", err) - } + require.NoErrorf(t, ValidateAgainstSchema(chrt, vals), "expected no error when subchart values are missing, got") } func TestValidateAgainstSchema_SubchartNil_NoPanic(t *testing.T) { @@ -351,14 +287,10 @@ func TestValidateAgainstSchema_SubchartNil_NoPanic(t *testing.T) { } defer func() { - if r := recover(); r != nil { - t.Fatalf("ValidateAgainstSchema panicked (nil subchart values): %v", r) - } + require.Nilf(t, recover(), "ValidateAgainstSchema panicked (nil subchart values)") }() - if err := ValidateAgainstSchema(chrt, vals); err != nil { - t.Fatalf("expected no error when subchart values are nil, got: %v", err) - } + require.NoErrorf(t, ValidateAgainstSchema(chrt, vals), "expected no error when subchart values are nil, got") } func TestValidateAgainstSchema_InvalidSubchartValuesType_NoPanic(t *testing.T) { @@ -379,13 +311,9 @@ func TestValidateAgainstSchema_InvalidSubchartValuesType_NoPanic(t *testing.T) { } defer func() { - if r := recover(); r != nil { - t.Fatalf("ValidateAgainstSchema panicked (invalid subchart values type): %v", r) - } + require.Nilf(t, recover(), "ValidateAgainstSchema panicked (invalid subchart values type)") }() // We expect a non-nil error (invalid type), but crucially no panic. - if err := ValidateAgainstSchema(chrt, vals); err == nil { - t.Fatal("expected an error when subchart values have invalid type, got nil") - } + require.Error(t, ValidateAgainstSchema(chrt, vals), "expected an error when subchart values have invalid type, got nil") } diff --git a/pkg/cmd/completion_test.go b/pkg/cmd/completion_test.go index 399ff1f0c..219d1eed0 100644 --- a/pkg/cmd/completion_test.go +++ b/pkg/cmd/completion_test.go @@ -21,6 +21,8 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/release/common" release "helm.sh/helm/v4/pkg/release/v1" @@ -44,9 +46,7 @@ func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) { testcmd := fmt.Sprintf("__complete %s ''", cmdName) _, out, err := executeActionCommandC(storage, testcmd) - if err != nil { - t.Errorf("unexpected error, %s", err) - } + require.NoError(t, err) if !strings.Contains(out, "ShellCompDirectiveNoFileComp") != shouldBePerformed { if shouldBePerformed { t.Errorf("Unexpected directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName) From 2f5a33fb77daca9ade910ed7fe557e3e85fd77f6 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:55 +0200 Subject: [PATCH 466/571] chore(pkg): refactor: convert tests to testify assert/require part 10 #### Description refactor: convert tests to testify assert/require in pkg Signed-off-by: Matthieu MOREL --- pkg/cmd/create_test.go | 155 +++++++---------------- pkg/storage/storage_test.go | 242 +++++++++++++----------------------- 2 files changed, 134 insertions(+), 263 deletions(-) diff --git a/pkg/cmd/create_test.go b/pkg/cmd/create_test.go index 1a1289775..6f25d4c63 100644 --- a/pkg/cmd/create_test.go +++ b/pkg/cmd/create_test.go @@ -22,6 +22,9 @@ import ( "slices" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chartv3 "helm.sh/helm/v4/internal/chart/v3" chartutilv3 "helm.sh/helm/v4/internal/chart/v3/util" "helm.sh/helm/v4/internal/gates" @@ -39,38 +42,25 @@ func TestCreateCmd(t *testing.T) { cname := "testchart" // Run a create - if _, _, err := executeActionCommand("create " + cname); err != nil { - t.Fatalf("Failed to run create: %s", err) - } + _, _, err := executeActionCommand("create " + cname) + require.NoErrorf(t, err, "Failed to run create") // Test that the chart is there - if fi, err := os.Stat(cname); err != nil { - t.Fatalf("no chart directory: %s", err) - } else if !fi.IsDir() { - t.Fatal("chart is not directory") - } + fi, err := os.Stat(cname) + require.NoErrorf(t, err, "no chart directory") + require.Truef(t, fi.IsDir(), "chart is not directory") c, err := chartloader.LoadDir(cname) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) acc, err := chart.NewAccessor(c) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if acc.Name() != cname { - t.Errorf("Expected %q name, got %q", cname, acc.Name()) - } + assert.Equal(t, cname, acc.Name(), "Expected %q name, got %q", cname, acc.Name()) metadata := acc.MetadataAsMap() apiVersion, ok := metadata["APIVersion"].(string) - if !ok { - t.Fatal("APIVersion not found in metadata") - } - if apiVersion != chartv2.APIVersionV2 { - t.Errorf("Wrong API version: %q", apiVersion) - } + require.True(t, ok, "APIVersion not found in metadata") + assert.Equal(t, chartv2.APIVersionV2, apiVersion, "Wrong API version: %q", apiVersion) } func TestCreateStarterCmd(t *testing.T) { @@ -123,15 +113,11 @@ func TestCreateStarterCmd(t *testing.T) { } else { dest, err = chartutil.Create("starterchart", starterchart) } - if err != nil { - t.Fatalf("Could not create chart: %s", err) - } + require.NoError(t, err, "Could not create chart") t.Logf("Created %s", dest) tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") - if err := os.WriteFile(tplpath, []byte("test"), 0o644); err != nil { - t.Fatalf("Could not write template: %s", err) - } + require.NoErrorf(t, os.WriteFile(tplpath, []byte("test"), 0o644), "Could not write template") // Build the command starterArg := "starterchart" @@ -147,51 +133,36 @@ func TestCreateStarterCmd(t *testing.T) { cmd += " " + cname // Run create - if _, _, err := executeActionCommand(cmd); err != nil { - t.Fatalf("Failed to run create: %s", err) - } + _, _, err = executeActionCommand(cmd) + require.NoErrorf(t, err, "Failed to run create") // Test that the chart is there - if fi, err := os.Stat(cname); err != nil { - t.Fatalf("no chart directory: %s", err) - } else if !fi.IsDir() { - t.Fatal("chart is not directory") - } + fi, err := os.Stat(cname) + require.NoErrorf(t, err, "no chart directory") + require.Truef(t, fi.IsDir(), "chart is not directory") // Load and verify the chart c, err := chartloader.LoadDir(cname) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) acc, err := chart.NewAccessor(c) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) chartName := acc.Name() metadata := acc.MetadataAsMap() apiVersion, ok := metadata["APIVersion"].(string) - if !ok { - t.Fatal("APIVersion not found in metadata") - } + require.True(t, ok, "APIVersion not found in metadata") var templates []string for _, tpl := range acc.Templates() { templates = append(templates, tpl.Name) } - if chartName != cname { - t.Errorf("Expected %q name, got %q", cname, chartName) - } - if apiVersion != tt.expectedVersion { - t.Errorf("Wrong API version: expected %q, got %q", tt.expectedVersion, apiVersion) - } + assert.Equal(t, cname, chartName, "Expected %q name, got %q", cname, chartName) + assert.Equal(t, tt.expectedVersion, apiVersion, "Wrong API version: expected %q, got %q", tt.expectedVersion, apiVersion) // Verify custom template exists found := slices.Contains(templates, "templates/foo.tpl") - if !found { - t.Error("Did not find foo.tpl") - } + assert.True(t, found, "Did not find foo.tpl") }) } } @@ -207,38 +178,25 @@ func TestCreateCmdChartAPIVersionV2(t *testing.T) { cname := "testchart" // Run a create with explicit v2 - if _, _, err := executeActionCommand("create --chart-api-version=v2 " + cname); err != nil { - t.Fatalf("Failed to run create: %s", err) - } + _, _, err := executeActionCommand("create --chart-api-version=v2 " + cname) + require.NoErrorf(t, err, "Failed to run create") // Test that the chart is there - if fi, err := os.Stat(cname); err != nil { - t.Fatalf("no chart directory: %s", err) - } else if !fi.IsDir() { - t.Fatal("chart is not directory") - } + fi, err := os.Stat(cname) + require.NoErrorf(t, err, "no chart directory") + require.Truef(t, fi.IsDir(), "chart is not directory") c, err := chartloader.LoadDir(cname) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) acc, err := chart.NewAccessor(c) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if acc.Name() != cname { - t.Errorf("Expected %q name, got %q", cname, acc.Name()) - } + assert.Equal(t, cname, acc.Name(), "Expected %q name, got %q", cname, acc.Name()) metadata := acc.MetadataAsMap() apiVersion, ok := metadata["APIVersion"].(string) - if !ok { - t.Fatal("APIVersion not found in metadata") - } - if apiVersion != chartv2.APIVersionV2 { - t.Errorf("Wrong API version: expected %q, got %q", chartv2.APIVersionV2, apiVersion) - } + require.True(t, ok, "APIVersion not found in metadata") + assert.Equal(t, chartv2.APIVersionV2, apiVersion, "Wrong API version: expected %q, got %q", chartv2.APIVersionV2, apiVersion) } func TestCreateCmdChartAPIVersionV3(t *testing.T) { @@ -248,38 +206,25 @@ func TestCreateCmdChartAPIVersionV3(t *testing.T) { cname := "testchart" // Run a create with v3 - if _, _, err := executeActionCommand("create --chart-api-version=v3 " + cname); err != nil { - t.Fatalf("Failed to run create: %s", err) - } + _, _, err := executeActionCommand("create --chart-api-version=v3 " + cname) + require.NoErrorf(t, err, "Failed to run create") // Test that the chart is there - if fi, err := os.Stat(cname); err != nil { - t.Fatalf("no chart directory: %s", err) - } else if !fi.IsDir() { - t.Fatal("chart is not directory") - } + fi, err := os.Stat(cname) + require.NoErrorf(t, err, "no chart directory") + require.Truef(t, fi.IsDir(), "chart is not directory") c, err := chartloader.LoadDir(cname) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) acc, err := chart.NewAccessor(c) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if acc.Name() != cname { - t.Errorf("Expected %q name, got %q", cname, acc.Name()) - } + assert.Equal(t, cname, acc.Name(), "Expected %q name, got %q", cname, acc.Name()) metadata := acc.MetadataAsMap() apiVersion, ok := metadata["APIVersion"].(string) - if !ok { - t.Fatal("APIVersion not found in metadata") - } - if apiVersion != chartv3.APIVersionV3 { - t.Errorf("Wrong API version: expected %q, got %q", chartv3.APIVersionV3, apiVersion) - } + require.True(t, ok, "APIVersion not found in metadata") + assert.Equal(t, chartv3.APIVersionV3, apiVersion, "Wrong API version: expected %q, got %q", chartv3.APIVersionV3, apiVersion) } func TestCreateCmdInvalidChartAPIVersion(t *testing.T) { @@ -289,12 +234,8 @@ func TestCreateCmdInvalidChartAPIVersion(t *testing.T) { // Run a create with invalid version _, _, err := executeActionCommand("create --chart-api-version=v1 " + cname) - if err == nil { - t.Fatal("Expected error for invalid API version, got nil") - } + require.Error(t, err, "Expected error for invalid API version, got nil") expectedErr := "unsupported chart API version: v1 (supported: v2, v3)" - if err.Error() != expectedErr { - t.Errorf("Expected error %q, got %q", expectedErr, err.Error()) - } + assert.EqualError(t, err, expectedErr, "Expected error %q, got %q", expectedErr, err.Error()) } diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index 8ecee447f..821eccb81 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -19,11 +19,11 @@ package storage import ( "context" "errors" - "fmt" "log/slog" "reflect" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/release" @@ -42,16 +42,14 @@ func TestStorageCreate(t *testing.T) { Version: 1, }.ToRelease() - assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease") + require.NoError(t, storage.Create(rls), "StoreRelease") // fetch the release res, err := storage.Get(rls.Name, rls.Version) - assertErrNil(t.Fatal, err, "QueryRelease") + require.NoError(t, err, "QueryRelease") // verify the fetched and created release are the same - if !reflect.DeepEqual(rls, res) { - t.Fatalf("Expected %v, got %v", rls, res) - } + require.Truef(t, reflect.DeepEqual(rls, res), "Expected %v, got %v", rls, res) } func TestStorageUpdate(t *testing.T) { @@ -65,20 +63,18 @@ func TestStorageUpdate(t *testing.T) { Status: common.StatusDeployed, }.ToRelease() - assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease") + require.NoError(t, storage.Create(rls), "StoreRelease") // modify the release rls.Info.Status = common.StatusUninstalled - assertErrNil(t.Fatal, storage.Update(rls), "UpdateRelease") + require.NoError(t, storage.Update(rls), "UpdateRelease") // retrieve the updated release res, err := storage.Get(rls.Name, rls.Version) - assertErrNil(t.Fatal, err, "QueryRelease") + require.NoError(t, err, "QueryRelease") // verify updated and fetched releases are the same. - if !reflect.DeepEqual(rls, res) { - t.Fatalf("Expected %v, got %v", rls, res) - } + require.Truef(t, reflect.DeepEqual(rls, res), "Expected %v, got %v", rls, res) } func TestStorageDelete(t *testing.T) { @@ -95,34 +91,26 @@ func TestStorageDelete(t *testing.T) { Version: 2, }.ToRelease() - assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease") - assertErrNil(t.Fatal, storage.Create(rls2), "StoreRelease") + require.NoError(t, storage.Create(rls), "StoreRelease") + require.NoError(t, storage.Create(rls2), "StoreRelease") // delete the release res, err := storage.Delete(rls.Name, rls.Version) - assertErrNil(t.Fatal, err, "DeleteRelease") + require.NoError(t, err, "DeleteRelease") // verify updated and fetched releases are the same. - if !reflect.DeepEqual(rls, res) { - t.Fatalf("Expected %v, got %v", rls, res) - } + require.Truef(t, reflect.DeepEqual(rls, res), "Expected %v, got %v", rls, res) hist, err := storage.History(rls.Name) - if err != nil { - t.Errorf("unexpected error: %s", err) - } + require.NoError(t, err) rhist, err := releaseListToV1List(hist) require.NoError(t, err) // We have now deleted one of the two records. - if len(rhist) != 1 { - t.Errorf("expected 1 record for deleted release version, got %d", len(hist)) - } + assert.Len(t, rhist, 1, "expected 1 record for deleted release version, got %d", len(hist)) - if rhist[0].Version != 2 { - t.Errorf("Expected version to be 2, got %d", rhist[0].Version) - } + assert.Equal(t, 2, rhist[0].Version, "Expected version to be 2, got %d", rhist[0].Version) } func TestStorageList(t *testing.T) { @@ -141,13 +129,13 @@ func TestStorageList(t *testing.T) { rls6 := ReleaseTestData{Name: "happy-liger", Status: common.StatusUninstalled}.ToRelease() // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'rls0'") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'rls1'") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'rls2'") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'rls3'") - assertErrNil(t.Fatal, storage.Create(rls4), "Storing release 'rls4'") - assertErrNil(t.Fatal, storage.Create(rls5), "Storing release 'rls5'") - assertErrNil(t.Fatal, storage.Create(rls6), "Storing release 'rls6'") + require.NoError(t, storage.Create(rls0), "Storing release 'rls0'") + require.NoError(t, storage.Create(rls1), "Storing release 'rls1'") + require.NoError(t, storage.Create(rls2), "Storing release 'rls2'") + require.NoError(t, storage.Create(rls3), "Storing release 'rls3'") + require.NoError(t, storage.Create(rls4), "Storing release 'rls4'") + require.NoError(t, storage.Create(rls5), "Storing release 'rls5'") + require.NoError(t, storage.Create(rls6), "Storing release 'rls6'") } var listTests = []struct { @@ -164,14 +152,9 @@ func TestStorageList(t *testing.T) { for _, tt := range listTests { list, err := tt.ListFunc() - assertErrNil(t.Fatal, err, tt.Description) + require.NoError(t, err, tt.Description) // verify the count of releases returned - if len(list) != tt.NumExpected { - t.Errorf("ListReleases(%s): expected %d, actual %d", - tt.Description, - tt.NumExpected, - len(list)) - } + assert.Len(t, list, tt.NumExpected, "ListReleases(%s): expected %d, actual %d", tt.Description, tt.NumExpected, len(list)) } } @@ -190,32 +173,24 @@ func TestStorageDeployed(t *testing.T) { rls3 := ReleaseTestData{Name: name, Version: 4, Status: common.StatusDeployed}.ToRelease() // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") + require.NoError(t, storage.Create(rls0), "Storing release 'angry-bird' (v1)") + require.NoError(t, storage.Create(rls1), "Storing release 'angry-bird' (v2)") + require.NoError(t, storage.Create(rls2), "Storing release 'angry-bird' (v3)") + require.NoError(t, storage.Create(rls3), "Storing release 'angry-bird' (v4)") } setup() rls, err := storage.Last(name) - if err != nil { - t.Fatalf("Failed to query for deployed release: %s\n", err) - } + require.NoError(t, err, "Failed to query for deployed release") rel, err := releaserToV1Release(rls) require.NoError(t, err) - switch { - case rls == nil: - t.Fatal("Release is nil") - case rel.Name != name: - t.Fatalf("Expected release name %q, actual %q\n", name, rel.Name) - case rel.Version != vers: - t.Fatalf("Expected release version %d, actual %d\n", vers, rel.Version) - case rel.Info.Status != common.StatusDeployed: - t.Fatalf("Expected release status 'DEPLOYED', actual %s\n", rel.Info.Status.String()) - } + require.NotNil(t, rls, "Release is nil") + require.Equal(t, name, rel.Name, "Expected release name %q, actual %q\n", name, rel.Name) + require.Equal(t, vers, rel.Version, "Expected release version %d, actual %d\n", vers, rel.Version) + require.Equal(t, common.StatusDeployed, rel.Info.Status, "Expected release status 'DEPLOYED', actual %s\n", rel.Info.Status.String()) } func TestStorageDeployedWithCorruption(t *testing.T) { @@ -233,32 +208,24 @@ func TestStorageDeployedWithCorruption(t *testing.T) { rls3 := ReleaseTestData{Name: name, Version: 2, Status: common.StatusDeployed}.ToRelease() // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") + require.NoError(t, storage.Create(rls0), "Storing release 'angry-bird' (v1)") + require.NoError(t, storage.Create(rls1), "Storing release 'angry-bird' (v2)") + require.NoError(t, storage.Create(rls2), "Storing release 'angry-bird' (v3)") + require.NoError(t, storage.Create(rls3), "Storing release 'angry-bird' (v4)") } setup() rls, err := storage.Deployed(name) - if err != nil { - t.Fatalf("Failed to query for deployed release: %s\n", err) - } + require.NoError(t, err, "Failed to query for deployed release") rel, err := releaserToV1Release(rls) require.NoError(t, err) - switch { - case rls == nil: - t.Fatal("Release is nil") - case rel.Name != name: - t.Fatalf("Expected release name %q, actual %q\n", name, rel.Name) - case rel.Version != vers: - t.Fatalf("Expected release version %d, actual %d\n", vers, rel.Version) - case rel.Info.Status != common.StatusDeployed: - t.Fatalf("Expected release status 'DEPLOYED', actual %s\n", rel.Info.Status.String()) - } + require.NotNil(t, rls, "Release is nil") + require.Equal(t, name, rel.Name, "Expected release name %q, actual %q\n", name, rel.Name) + require.Equal(t, vers, rel.Version, "Expected release version %d, actual %d\n", vers, rel.Version) + require.Equal(t, common.StatusDeployed, rel.Info.Status, "Expected release status 'DEPLOYED', actual %s\n", rel.Info.Status.String()) } func TestStorageHistory(t *testing.T) { @@ -275,21 +242,17 @@ func TestStorageHistory(t *testing.T) { rls3 := ReleaseTestData{Name: name, Version: 4, Status: common.StatusDeployed}.ToRelease() // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") + require.NoError(t, storage.Create(rls0), "Storing release 'angry-bird' (v1)") + require.NoError(t, storage.Create(rls1), "Storing release 'angry-bird' (v2)") + require.NoError(t, storage.Create(rls2), "Storing release 'angry-bird' (v3)") + require.NoError(t, storage.Create(rls3), "Storing release 'angry-bird' (v4)") } setup() h, err := storage.History(name) - if err != nil { - t.Fatalf("Failed to query for release history (%q): %s\n", name, err) - } - if len(h) != 4 { - t.Fatalf("Release history (%q) is empty\n", name) - } + require.NoError(t, err, "Failed to query for release history (%q)", name) + require.Len(t, h, 4, "Release history (%q) is empty\n", name) } var errMaxHistoryMockDriverSomethingHappened = errors.New("something happened") @@ -336,16 +299,14 @@ func TestMaxHistoryErrorHandling(t *testing.T) { rls1 := ReleaseTestData{Name: name, Version: 1, Status: common.StatusSuperseded}.ToRelease() // create the release records in the storage - assertErrNil(t.Fatal, storage.Driver.Create(makeKey(rls1.Name, rls1.Version), rls1), "Storing release 'angry-bird' (v1)") + require.NoError(t, storage.Driver.Create(makeKey(rls1.Name, rls1.Version), rls1), "Storing release 'angry-bird' (v1)") } setup() rls2 := ReleaseTestData{Name: name, Version: 2, Status: common.StatusSuperseded}.ToRelease() wantErr := errMaxHistoryMockDriverSomethingHappened gotErr := storage.Create(rls2) - if !errors.Is(gotErr, wantErr) { - t.Fatalf("Storing release 'angry-bird' (v2) should return the error %#v, but returned %#v", wantErr, gotErr) - } + require.ErrorIs(t, gotErr, wantErr, "Storing release 'angry-bird' (v2) should return the error %#v, but returned %#v", wantErr, gotErr) } func TestStorageRemoveLeastRecent(t *testing.T) { @@ -365,27 +326,25 @@ func TestStorageRemoveLeastRecent(t *testing.T) { rls3 := ReleaseTestData{Name: name, Version: 4, Status: common.StatusDeployed}.ToRelease() // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") + require.NoError(t, storage.Create(rls0), "Storing release 'angry-bird' (v1)") + require.NoError(t, storage.Create(rls1), "Storing release 'angry-bird' (v2)") + require.NoError(t, storage.Create(rls2), "Storing release 'angry-bird' (v3)") + require.NoError(t, storage.Create(rls3), "Storing release 'angry-bird' (v4)") } setup() // Because we have not set a limit, we expect 4. expect := 4 - if hist, err := storage.History(name); err != nil { - t.Fatal(err) - } else if len(hist) != expect { - t.Fatalf("expected %d items in history, got %d", expect, len(hist)) - } + hist, err := storage.History(name) + require.NoError(t, err) + require.Equal(t, len(hist), expect, "expected %d items in history, got %d", expect, len(hist)) storage.MaxHistory = 3 rls5 := ReleaseTestData{Name: name, Version: 5, Status: common.StatusDeployed}.ToRelease() - assertErrNil(t.Fatal, storage.Create(rls5), "Storing release 'angry-bird' (v5)") + require.NoError(t, storage.Create(rls5), "Storing release 'angry-bird' (v5)") // On inserting the 5th record, we expect two records to be pruned from history. - hist, err := storage.History(name) + hist, err = storage.History(name) require.NoError(t, err) rhist, err := releaseListToV1List(hist) require.NoError(t, err) @@ -399,9 +358,8 @@ func TestStorageRemoveLeastRecent(t *testing.T) { // We expect the existing records to be 3, 4, and 5. for i, item := range rhist { v := item.Version - if expect := i + 3; v != expect { - t.Errorf("Expected release %d, got %d", expect, v) - } + expect := i + 3 + assert.Equalf(t, v, expect, "Expected release %d, got %d", expect, v) } } @@ -420,22 +378,21 @@ func TestStorageDoNotDeleteDeployed(t *testing.T) { rls3 := ReleaseTestData{Name: name, Version: 4, Status: common.StatusFailed}.ToRelease() // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") + require.NoError(t, storage.Create(rls0), "Storing release 'angry-bird' (v1)") + require.NoError(t, storage.Create(rls1), "Storing release 'angry-bird' (v2)") + require.NoError(t, storage.Create(rls2), "Storing release 'angry-bird' (v3)") + require.NoError(t, storage.Create(rls3), "Storing release 'angry-bird' (v4)") } setup() rls5 := ReleaseTestData{Name: name, Version: 5, Status: common.StatusFailed}.ToRelease() - assertErrNil(t.Fatal, storage.Create(rls5), "Storing release 'angry-bird' (v5)") + require.NoError(t, storage.Create(rls5), "Storing release 'angry-bird' (v5)") // On inserting the 5th record, we expect a total of 3 releases, but we expect version 2 // (the only deployed release), to still exist hist, err := storage.History(name) - if err != nil { - t.Fatal(err) - } else if len(hist) != storage.MaxHistory { + require.NoError(t, err) + if len(hist) != storage.MaxHistory { rhist, err := releaseListToV1List(hist) require.NoError(t, err) for _, item := range rhist { @@ -453,9 +410,7 @@ func TestStorageDoNotDeleteDeployed(t *testing.T) { rhist, err := releaseListToV1List(hist) require.NoError(t, err) for _, item := range rhist { - if !expectedVersions[item.Version] { - t.Errorf("Release version %d, found when not expected", item.Version) - } + assert.Truef(t, expectedVersions[item.Version], "Release version %d, found when not expected", item.Version) } } @@ -473,25 +428,21 @@ func TestStorageLast(t *testing.T) { rls3 := ReleaseTestData{Name: name, Version: 4, Status: common.StatusFailed}.ToRelease() // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") + require.NoError(t, storage.Create(rls0), "Storing release 'angry-bird' (v1)") + require.NoError(t, storage.Create(rls1), "Storing release 'angry-bird' (v2)") + require.NoError(t, storage.Create(rls2), "Storing release 'angry-bird' (v3)") + require.NoError(t, storage.Create(rls3), "Storing release 'angry-bird' (v4)") } setup() h, err := storage.Last(name) - if err != nil { - t.Fatalf("Failed to query for release history (%q): %s\n", name, err) - } + require.NoError(t, err, "Failed to query for release history (%q)", name) rel, err := releaserToV1Release(h) require.NoError(t, err) - if rel.Version != 4 { - t.Errorf("Expected revision 4, got %d", rel.Version) - } + assert.Equal(t, 4, rel.Version, "Expected revision 4, got %d", rel.Version) } // TestUpgradeInitiallyFailedReleaseWithHistoryLimit tests a case when there are no deployed release yet, but history limit has been @@ -511,47 +462,34 @@ func TestUpgradeInitiallyFailedReleaseWithHistoryLimit(t *testing.T) { rls3 := ReleaseTestData{Name: name, Version: 4, Status: common.StatusFailed}.ToRelease() // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") + require.NoError(t, storage.Create(rls0), "Storing release 'angry-bird' (v1)") + require.NoError(t, storage.Create(rls1), "Storing release 'angry-bird' (v2)") + require.NoError(t, storage.Create(rls2), "Storing release 'angry-bird' (v3)") + require.NoError(t, storage.Create(rls3), "Storing release 'angry-bird' (v4)") hist, err := storage.History(name) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } + require.NoError(t, err) wantHistoryLen := 4 - if len(hist) != wantHistoryLen { - t.Fatalf("expected history of release %q to contain %d releases, got %d", name, wantHistoryLen, len(hist)) - } + require.Len(t, hist, wantHistoryLen, "expected history of release %q to contain %d releases, got %d", name, wantHistoryLen, len(hist)) } setup() rls5 := ReleaseTestData{Name: name, Version: 5, Status: common.StatusFailed}.ToRelease() - err := storage.Create(rls5) - if err != nil { - t.Fatalf("Failed to create a new release version: %s", err) - } + require.NoError(t, storage.Create(rls5), "Failed to create a new release version") hist, err := storage.History(name) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } + require.NoError(t, err) rhist, err := releaseListToV1List(hist) require.NoError(t, err) for i, rel := range rhist { wantVersion := i + 2 - if rel.Version != wantVersion { - t.Fatalf("Expected history release %d version to equal %d, got %d", i+1, wantVersion, rel.Version) - } + require.Equal(t, wantVersion, rel.Version, "Expected history release %d version to equal %d, got %d", i+1, wantVersion, rel.Version) wantStatus := common.StatusFailed - if rel.Info.Status != wantStatus { - t.Fatalf("Expected history release %d status to equal %q, got %q", i+1, wantStatus, rel.Info.Status) - } + require.Equal(t, wantStatus, rel.Info.Status, "Expected history release %d status to equal %q, got %q", i+1, wantStatus, rel.Info.Status) } } @@ -573,21 +511,13 @@ func (test ReleaseTestData) ToRelease() *rspb.Release { } } -func assertErrNil(eh func(args ...any), err error, message string) { - if err != nil { - eh(fmt.Sprintf("%s: %q", message, err)) - } -} - func TestStorageGetsLoggerFromDriver(t *testing.T) { d := driver.NewMemory() l := &mockSLogHandler{} d.SetLogger(l) s := Init(d) _, _ = s.Get("doesnt-matter", 123) - if !l.Called { - t.Fatal("Expected storage to use driver's logger, but it did not") - } + require.True(t, l.Called, "Expected storage to use driver's logger, but it did not") } type mockSLogHandler struct { From dc3e3a5e7efadff49fd26eac511cf18801af0ebc Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:56 +0200 Subject: [PATCH 467/571] chore(pkg): refactor: convert tests to testify assert/require part 11 refactor: convert tests to testify assert/require in pkg Signed-off-by: Matthieu MOREL --- pkg/action/rollback_test.go | 3 +- pkg/downloader/manager_test.go | 217 +++++++++------------------------ pkg/kube/ready_test.go | 202 +++++++++++------------------- 3 files changed, 128 insertions(+), 294 deletions(-) diff --git a/pkg/action/rollback_test.go b/pkg/action/rollback_test.go index 01419341b..7ffd90d25 100644 --- a/pkg/action/rollback_test.go +++ b/pkg/action/rollback_test.go @@ -78,8 +78,7 @@ func TestRollback_WaitOptionsPassedDownstream(t *testing.T) { // Access the underlying FailingKubeClient to check recorded options failer := config.KubeClient.(*kubefake.FailingKubeClient) - err := client.Run(rel.Name) - req.NoError(err) + req.NoError(client.Run(rel.Name)) // Verify that WaitOptions were passed to GetWaiter is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index e0cea0cd2..00cb5b668 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -17,7 +17,6 @@ package downloader import ( "bytes" - "errors" "io/fs" "os" "path/filepath" @@ -50,9 +49,7 @@ func TestVersionEquals(t *testing.T) { } for _, tt := range tests { - if versionEquals(tt.v1, tt.v2) != tt.expect { - t.Errorf("%s: failed comparison of %q and %q (expect equal: %t)", tt.name, tt.v1, tt.v2, tt.expect) - } + assert.Equal(t, tt.expect, versionEquals(tt.v1, tt.v2), "%s: failed comparison of %q and %q (expect equal: %t)", tt.name, tt.v1, tt.v2, tt.expect) } } @@ -64,84 +61,46 @@ func TestFindChartURL(t *testing.T) { RepositoryCache: repoCache, } repos, err := m.loadChartRepositories() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) name := "alpine" version := "0.1.0" repoURL := "http://example.com/charts" churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err := m.findChartURL(name, version, repoURL, repos) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if churl != "https://charts.helm.sh/stable/alpine-0.1.0.tgz" { - t.Errorf("Unexpected URL %q", churl) - } - if username != "" { - t.Errorf("Unexpected username %q", username) - } - if password != "" { - t.Errorf("Unexpected password %q", password) - } - if passcredentialsall != false { - t.Errorf("Unexpected passcredentialsall %t", passcredentialsall) - } - if insecureSkipTLSVerify { - t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify) - } + assert.Equal(t, "https://charts.helm.sh/stable/alpine-0.1.0.tgz", churl, "Unexpected URL %q", churl) + assert.Empty(t, username, "Unexpected username %q", username) + assert.Empty(t, password, "Unexpected password %q", password) + assert.False(t, passcredentialsall, "Unexpected passcredentialsall %t", passcredentialsall) + assert.False(t, insecureSkipTLSVerify, "Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify) name = "tlsfoo" version = "1.2.3" repoURL = "https://example-https-insecureskiptlsverify.com" churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err = m.findChartURL(name, version, repoURL, repos) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if !insecureSkipTLSVerify { - t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify) - } - if churl != "https://example.com/tlsfoo-1.2.3.tgz" { - t.Errorf("Unexpected URL %q", churl) - } - if username != "" { - t.Errorf("Unexpected username %q", username) - } - if password != "" { - t.Errorf("Unexpected password %q", password) - } - if passcredentialsall != false { - t.Errorf("Unexpected passcredentialsall %t", passcredentialsall) - } + assert.True(t, insecureSkipTLSVerify, "Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify) + assert.Equal(t, "https://example.com/tlsfoo-1.2.3.tgz", churl, "Unexpected URL %q", churl) + assert.Empty(t, username, "Unexpected username %q", username) + assert.Empty(t, password, "Unexpected password %q", password) + assert.False(t, passcredentialsall, "Unexpected passcredentialsall %t", passcredentialsall) name = "foo" version = "1.2.3" repoURL = "http://example.com/helm" churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err = m.findChartURL(name, version, repoURL, repos) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if churl != "http://example.com/helm/charts/foo-1.2.3.tgz" { - t.Errorf("Unexpected URL %q", churl) - } - if username != "" { - t.Errorf("Unexpected username %q", username) - } - if password != "" { - t.Errorf("Unexpected password %q", password) - } - if passcredentialsall != false { - t.Errorf("Unexpected passcredentialsall %t", passcredentialsall) - } - if insecureSkipTLSVerify { - t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify) - } + assert.Equal(t, "http://example.com/helm/charts/foo-1.2.3.tgz", churl, "Unexpected URL %q", churl) + assert.Empty(t, username, "Unexpected username %q", username) + assert.Empty(t, password, "Unexpected password %q", password) + assert.False(t, passcredentialsall, "Unexpected passcredentialsall %t", passcredentialsall) + assert.False(t, insecureSkipTLSVerify, "Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify) } func TestGetRepoNames(t *testing.T) { @@ -217,15 +176,11 @@ func TestGetRepoNames(t *testing.T) { t.Fatal(err) } - if tt.err { - t.Fatalf("Expected error in test %q", tt.name) - } + require.False(t, tt.err, "Expected error in test %q", tt.name) // m1 and m2 are the maps we want to compare eq := reflect.DeepEqual(l, tt.expect) - if !eq { - t.Errorf("%s: expected map %v, got %v", tt.name, l, tt.name) - } + assert.True(t, eq, "%s: expected map %v, got %v", tt.name, l, tt.name) } } @@ -238,20 +193,12 @@ func TestDownloadAll(t *testing.T) { ChartPath: chartPath, } signtest, err := loader.LoadDir(filepath.Join("testdata", "signtest")) - if err != nil { - t.Fatal(err) - } - if err := chartutil.SaveDir(signtest, filepath.Join(chartPath, "testdata")); err != nil { - t.Fatal(err) - } + require.NoError(t, err) + require.NoError(t, chartutil.SaveDir(signtest, filepath.Join(chartPath, "testdata"))) local, err := loader.LoadDir(filepath.Join("testdata", "local-subchart")) - if err != nil { - t.Fatal(err) - } - if err := chartutil.SaveDir(local, filepath.Join(chartPath, "charts")); err != nil { - t.Fatal(err) - } + require.NoError(t, err) + require.NoError(t, chartutil.SaveDir(local, filepath.Join(chartPath, "charts"))) signDep := &chart.Dependency{ Name: signtest.Name(), @@ -265,16 +212,11 @@ func TestDownloadAll(t *testing.T) { } // create a 'tmpcharts' directory to test #5567 - if err := os.MkdirAll(filepath.Join(chartPath, "tmpcharts"), 0o755); err != nil { - t.Fatal(err) - } - if err := m.downloadAll([]*chart.Dependency{signDep, localDep}); err != nil { - t.Error(err) - } + require.NoError(t, os.MkdirAll(filepath.Join(chartPath, "tmpcharts"), 0o755)) + require.NoError(t, m.downloadAll([]*chart.Dependency{signDep, localDep})) - if _, err := os.Stat(filepath.Join(chartPath, "charts", "signtest-0.1.0.tgz")); errors.Is(err, fs.ErrNotExist) { - t.Error(err) - } + _, err = os.Stat(filepath.Join(chartPath, "charts", "signtest-0.1.0.tgz")) + require.NotErrorIs(t, err, fs.ErrNotExist) // A chart with a bad name like this cannot be loaded and saved. Handling in // the loading and saving will return an error about the invalid name. In @@ -283,24 +225,15 @@ func TestDownloadAll(t *testing.T) { description: A Helm chart for Kubernetes name: ../bad-local-subchart version: 0.1.0` - if err := os.MkdirAll(filepath.Join(chartPath, "testdata", "bad-local-subchart"), 0o755); err != nil { - t.Fatal(err) - } - err = os.WriteFile(filepath.Join(chartPath, "testdata", "bad-local-subchart", "Chart.yaml"), []byte(badchartyaml), 0o644) - if err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(filepath.Join(chartPath, "testdata", "bad-local-subchart"), 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(chartPath, "testdata", "bad-local-subchart", "Chart.yaml"), []byte(badchartyaml), 0o644)) badLocalDep := &chart.Dependency{ Name: "../bad-local-subchart", Repository: "file://./testdata/bad-local-subchart", Version: "0.1.0", } - - err = m.downloadAll([]*chart.Dependency{badLocalDep}) - if err == nil { - t.Fatal("Expected error for bad dependency name") - } + require.Error(t, m.downloadAll([]*chart.Dependency{badLocalDep}), "Expected error for bad dependency name") } func TestUpdateBeforeBuild(t *testing.T) { @@ -310,9 +243,7 @@ func TestUpdateBeforeBuild(t *testing.T) { repotest.WithChartSourceGlob("testdata/*.tgz*"), ) defer srv.Stop() - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.LinkIndices()) dir := func(p ...string) string { return filepath.Join(append([]string{srv.Root()}, p...)...) } @@ -325,9 +256,7 @@ func TestUpdateBeforeBuild(t *testing.T) { APIVersion: "v1", }, } - if err := chartutil.SaveDir(d, dir()); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(d, dir())) // Save a chart c := &chart.Chart{ Metadata: &chart.Metadata{ @@ -341,9 +270,7 @@ func TestUpdateBeforeBuild(t *testing.T) { }}, }, } - if err := chartutil.SaveDir(c, dir()); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(c, dir())) // Set-up a manager b := bytes.NewBuffer(nil) @@ -360,13 +287,8 @@ func TestUpdateBeforeBuild(t *testing.T) { } // Update before Build. see issue: https://github.com/helm/helm/issues/7101 - if err := m.Update(); err != nil { - t.Fatal(err) - } - - if err := m.Build(); err != nil { - t.Fatal(err) - } + require.NoError(t, m.Update()) + require.NoError(t, m.Build()) } // TestUpdateWithNoRepo is for the case of a dependency that has no repo listed. @@ -379,9 +301,7 @@ func TestUpdateWithNoRepo(t *testing.T) { repotest.WithChartSourceGlob("testdata/*.tgz*"), ) defer srv.Stop() - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.LinkIndices()) dir := func(p ...string) string { return filepath.Join(append([]string{srv.Root()}, p...)...) } @@ -407,16 +327,12 @@ func TestUpdateWithNoRepo(t *testing.T) { }}, }, } - if err := chartutil.SaveDir(c, dir()); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(c, dir())) // Save dependent chart into the parents charts directory. If the chart is // not in the charts directory Helm will return an error that it is not // found. - if err := chartutil.SaveDir(d, dir(c.Metadata.Name, "charts")); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(d, dir(c.Metadata.Name, "charts"))) // Set-up a manager b := bytes.NewBuffer(nil) @@ -433,9 +349,7 @@ func TestUpdateWithNoRepo(t *testing.T) { } // Test the update - if err := m.Update(); err != nil { - t.Fatal(err) - } + require.NoError(t, m.Update()) } // This function is the skeleton test code of failing tests for #6416 and #6871 and bugs due to #5874. @@ -452,9 +366,7 @@ func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Depe repotest.WithChartSourceGlob("testdata/*.tgz*"), ) defer srv.Stop() - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.LinkIndices()) dir := func(p ...string) string { return filepath.Join(append([]string{srv.Root()}, p...)...) } @@ -479,9 +391,7 @@ func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Depe Dependencies: []*chart.Dependency{&dep}, }, } - if err := chartutil.SaveDir(c, dir()); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(c, dir())) // Set-up a manager b := bytes.NewBuffer(nil) @@ -500,14 +410,10 @@ func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Depe } // First build will update dependencies and create Chart.lock file. - if err := m.Build(); err != nil { - t.Fatal(err) - } + require.NoError(t, m.Build()) // Second build should be passed. See PR #6655. - if err := m.Build(); err != nil { - t.Fatal(err) - } + require.NoError(t, m.Build()) } func TestBuild_WithoutOptionalFields(t *testing.T) { @@ -573,9 +479,8 @@ func TestErrRepoNotFound_Error(t *testing.T) { e := ErrRepoNotFound{ Repos: tt.fields.Repos, } - if got := e.Error(); got != tt.want { - t.Errorf("Error() = %v, want %v", got, tt.want) - } + got := e.Error() + assert.EqualErrorf(t, e, tt.want, "Error() = %v, want %v", got, tt.want) }) } } @@ -601,12 +506,8 @@ func TestKey(t *testing.T) { for _, tt := range tests { o, err := key(tt.name) - if err != nil { - t.Fatalf("unable to generate key for %q with error: %s", tt.name, err) - } - if o != tt.expect { - t.Errorf("wrong key name generated for %q, expected %q but got %q", tt.name, tt.expect, o) - } + require.NoError(t, err, "unable to generate key for %q", tt.name) + assert.Equal(t, tt.expect, o, "wrong key name generated for %q, expected %q but got %q", tt.name, tt.expect, o) } } @@ -697,8 +598,7 @@ func TestWriteLock(t *testing.T) { t.Run("v2 lock file", func(t *testing.T) { dir := t.TempDir() - err := writeLock(dir, lock, false) - require.NoError(t, err) + require.NoError(t, writeLock(dir, lock, false)) lockfilePath := filepath.Join(dir, "Chart.lock") _, err = os.Stat(lockfilePath) @@ -716,8 +616,7 @@ func TestWriteLock(t *testing.T) { t.Run("v1 lock file", func(t *testing.T) { dir := t.TempDir() - err := writeLock(dir, lock, true) - require.NoError(t, err) + require.NoError(t, writeLock(dir, lock, true)) lockfilePath := filepath.Join(dir, "requirements.lock") _, err = os.Stat(lockfilePath) @@ -737,9 +636,7 @@ func TestWriteLock(t *testing.T) { dir := t.TempDir() lockfilePath := filepath.Join(dir, "Chart.lock") require.NoError(t, os.WriteFile(lockfilePath, []byte("old content"), 0o644)) - - err = writeLock(dir, lock, false) - require.NoError(t, err) + require.NoError(t, writeLock(dir, lock, false)) content, err := os.ReadFile(lockfilePath) require.NoError(t, err) @@ -753,17 +650,13 @@ func TestWriteLock(t *testing.T) { lockfilePath := filepath.Join(dir, "Chart.lock") require.NoError(t, os.Symlink(dummyFile, lockfilePath)) - - err = writeLock(dir, lock, false) - assert.ErrorContains(t, err, "the Chart.lock file is a symlink to") + assert.ErrorContains(t, writeLock(dir, lock, false), "the Chart.lock file is a symlink to") }) t.Run("chart path is not a directory", func(t *testing.T) { dir := t.TempDir() filePath := filepath.Join(dir, "not-a-dir") require.NoError(t, os.WriteFile(filePath, []byte("file"), 0o644)) - - err = writeLock(filePath, lock, false) - assert.Error(t, err) + assert.Error(t, writeLock(filePath, lock, false)) }) } diff --git a/pkg/kube/ready_test.go b/pkg/kube/ready_test.go index fc3ac9409..01f6db3cb 100644 --- a/pkg/kube/ready_test.go +++ b/pkg/kube/ready_test.go @@ -19,6 +19,8 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" @@ -90,18 +92,15 @@ func Test_ReadyChecker_IsReady_Pod(t *testing.T) { checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } - if _, err := c.client.CoreV1().Pods(defaultNamespace).Create(t.Context(), tt.pod, metav1.CreateOptions{}); err != nil { - t.Errorf("Failed to create Pod error: %v", err) - return - } + _, err := c.client.CoreV1().Pods(defaultNamespace).Create(t.Context(), tt.pod, metav1.CreateOptions{}) + require.NoError(t, err, "Failed to create Pod error") got, err := c.IsReady(tt.args.ctx, tt.args.resource) - if (err != nil) != tt.wantErr { - t.Errorf("IsReady() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("IsReady() = %v, want %v", got, tt.want) + if tt.wantErr { + require.Error(t, err, "IsReady()") + } else { + require.NoError(t, err, "IsReady()") } + assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } } @@ -162,17 +161,12 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) { checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } - if _, err := c.client.BatchV1().Jobs(defaultNamespace).Create(t.Context(), tt.job, metav1.CreateOptions{}); err != nil { - t.Errorf("Failed to create Job error: %v", err) - return - } + _, err := c.client.BatchV1().Jobs(defaultNamespace).Create(t.Context(), tt.job, metav1.CreateOptions{}) + + require.NoError(t, err, "Failed to create Job") got, err := c.IsReady(tt.args.ctx, tt.args.resource) - if (err != nil) != tt.wantErr { - t.Errorf("IsReady() error = %v, wantErr %v", err, tt.wantErr) - } - if got != tt.want { - t.Errorf("IsReady() = %v, want %v", got, tt.want) - } + assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } } @@ -236,21 +230,15 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) { checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } - if _, err := c.client.AppsV1().Deployments(defaultNamespace).Create(t.Context(), tt.deployment, metav1.CreateOptions{}); err != nil { - t.Errorf("Failed to create Deployment error: %v", err) - return - } - if _, err := c.client.AppsV1().ReplicaSets(defaultNamespace).Create(t.Context(), tt.replicaSet, metav1.CreateOptions{}); err != nil { - t.Errorf("Failed to create ReplicaSet error: %v", err) - return - } + _, err := c.client.AppsV1().Deployments(defaultNamespace).Create(t.Context(), tt.deployment, metav1.CreateOptions{}) + require.NoError(t, err, "Failed to create Deployment") + + _, err = c.client.AppsV1().ReplicaSets(defaultNamespace).Create(t.Context(), tt.replicaSet, metav1.CreateOptions{}) + require.NoError(t, err, "Failed to create ReplicaSet") + got, err := c.IsReady(tt.args.ctx, tt.args.resource) - if (err != nil) != tt.wantErr { - t.Errorf("IsReady() error = %v, wantErr %v", err, tt.wantErr) - } - if got != tt.want { - t.Errorf("IsReady() = %v, want %v", got, tt.want) - } + assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } } @@ -311,17 +299,12 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) { checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } - if _, err := c.client.CoreV1().PersistentVolumeClaims(defaultNamespace).Create(t.Context(), tt.pvc, metav1.CreateOptions{}); err != nil { - t.Errorf("Failed to create PersistentVolumeClaim error: %v", err) - return - } + _, err := c.client.CoreV1().PersistentVolumeClaims(defaultNamespace).Create(t.Context(), tt.pvc, metav1.CreateOptions{}) + require.NoError(t, err, "Failed to create PersistentVolumeClaim") + got, err := c.IsReady(tt.args.ctx, tt.args.resource) - if (err != nil) != tt.wantErr { - t.Errorf("IsReady() error = %v, wantErr %v", err, tt.wantErr) - } - if got != tt.want { - t.Errorf("IsReady() = %v, want %v", got, tt.want) - } + assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } } @@ -382,17 +365,12 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) { checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } - if _, err := c.client.CoreV1().Services(defaultNamespace).Create(t.Context(), tt.svc, metav1.CreateOptions{}); err != nil { - t.Errorf("Failed to create Service error: %v", err) - return - } + _, err := c.client.CoreV1().Services(defaultNamespace).Create(t.Context(), tt.svc, metav1.CreateOptions{}) + require.NoError(t, err, "Failed to create Service") + got, err := c.IsReady(tt.args.ctx, tt.args.resource) - if (err != nil) != tt.wantErr { - t.Errorf("IsReady() error = %v, wantErr %v", err, tt.wantErr) - } - if got != tt.want { - t.Errorf("IsReady() = %v, want %v", got, tt.want) - } + assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } } @@ -453,17 +431,12 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) { checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } - if _, err := c.client.AppsV1().DaemonSets(defaultNamespace).Create(t.Context(), tt.ds, metav1.CreateOptions{}); err != nil { - t.Errorf("Failed to create DaemonSet error: %v", err) - return - } + _, err := c.client.AppsV1().DaemonSets(defaultNamespace).Create(t.Context(), tt.ds, metav1.CreateOptions{}) + require.NoError(t, err, "Failed to create DaemonSet") + got, err := c.IsReady(tt.args.ctx, tt.args.resource) - if (err != nil) != tt.wantErr { - t.Errorf("IsReady() error = %v, wantErr %v", err, tt.wantErr) - } - if got != tt.want { - t.Errorf("IsReady() = %v, want %v", got, tt.want) - } + assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } } @@ -524,17 +497,12 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) { checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } - if _, err := c.client.AppsV1().StatefulSets(defaultNamespace).Create(t.Context(), tt.ss, metav1.CreateOptions{}); err != nil { - t.Errorf("Failed to create StatefulSet error: %v", err) - return - } + _, err := c.client.AppsV1().StatefulSets(defaultNamespace).Create(t.Context(), tt.ss, metav1.CreateOptions{}) + require.NoError(t, err, "Failed to create StatefulSet") + got, err := c.IsReady(tt.args.ctx, tt.args.resource) - if (err != nil) != tt.wantErr { - t.Errorf("IsReady() error = %v, wantErr %v", err, tt.wantErr) - } - if got != tt.want { - t.Errorf("IsReady() = %v, want %v", got, tt.want) - } + assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } } @@ -610,17 +578,12 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) { checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } - if _, err := c.client.CoreV1().ReplicationControllers(defaultNamespace).Create(t.Context(), tt.rc, metav1.CreateOptions{}); err != nil { - t.Errorf("Failed to create ReplicationController error: %v", err) - return - } + _, err := c.client.CoreV1().ReplicationControllers(defaultNamespace).Create(t.Context(), tt.rc, metav1.CreateOptions{}) + require.NoError(t, err, "Failed to create ReplicationController") + got, err := c.IsReady(tt.args.ctx, tt.args.resource) - if (err != nil) != tt.wantErr { - t.Errorf("IsReady() error = %v, wantErr %v", err, tt.wantErr) - } - if got != tt.want { - t.Errorf("IsReady() = %v, want %v", got, tt.want) - } + assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } } @@ -683,12 +646,8 @@ func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) { } // got, err := c.IsReady(tt.args.ctx, tt.args.resource) - if (err != nil) != tt.wantErr { - t.Errorf("IsReady() error = %v, wantErr %v", err, tt.wantErr) - } - if got != tt.want { - t.Errorf("IsReady() = %v, want %v", got, tt.want) - } + assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } } @@ -755,9 +714,8 @@ func Test_ReadyChecker_deploymentReady(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := NewReadyChecker(fake.NewClientset()) - if got := c.deploymentReady(tt.args.rs, tt.args.dep); got != tt.want { - t.Errorf("deploymentReady() = %v, want %v", got, tt.want) - } + got := c.deploymentReady(tt.args.rs, tt.args.dep) + assert.Equalf(t, tt.want, got, "deploymentReady() = %v, want %v", got, tt.want) }) } } @@ -789,9 +747,8 @@ func Test_ReadyChecker_replicaSetReady(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := NewReadyChecker(fake.NewClientset()) - if got := c.replicaSetReady(tt.args.rs); got != tt.want { - t.Errorf("replicaSetReady() = %v, want %v", got, tt.want) - } + got := c.replicaSetReady(tt.args.rs) + assert.Equalf(t, tt.want, got, "replicaSetReady() = %v, want %v", got, tt.want) }) } } @@ -823,9 +780,8 @@ func Test_ReadyChecker_replicationControllerReady(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := NewReadyChecker(fake.NewClientset()) - if got := c.replicationControllerReady(tt.args.rc); got != tt.want { - t.Errorf("replicationControllerReady() = %v, want %v", got, tt.want) - } + got := c.replicationControllerReady(tt.args.rc) + assert.Equalf(t, tt.want, got, "replicationControllerReady() = %v, want %v", got, tt.want) }) } } @@ -878,9 +834,8 @@ func Test_ReadyChecker_daemonSetReady(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := NewReadyChecker(fake.NewClientset()) - if got := c.daemonSetReady(tt.args.ds); got != tt.want { - t.Errorf("daemonSetReady() = %v, want %v", got, tt.want) - } + got := c.daemonSetReady(tt.args.ds) + assert.Equalf(t, tt.want, got, "daemonSetReady() = %v, want %v", got, tt.want) }) } } @@ -954,9 +909,8 @@ func Test_ReadyChecker_statefulSetReady(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := NewReadyChecker(fake.NewClientset()) - if got := c.statefulSetReady(tt.args.sts); got != tt.want { - t.Errorf("statefulSetReady() = %v, want %v", got, tt.want) - } + got := c.statefulSetReady(tt.args.sts) + assert.Equalf(t, tt.want, got, "statefulSetReady() = %v, want %v", got, tt.want) }) } } @@ -1014,19 +968,16 @@ func Test_ReadyChecker_podsReadyForObject(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := NewReadyChecker(fake.NewClientset()) for _, pod := range tt.existPods { - if _, err := c.client.CoreV1().Pods(defaultNamespace).Create(t.Context(), &pod, metav1.CreateOptions{}); err != nil { - t.Errorf("Failed to create Pod error: %v", err) - return - } + _, err := c.client.CoreV1().Pods(defaultNamespace).Create(t.Context(), &pod, metav1.CreateOptions{}) + require.NoError(t, err, "Failed to create Pod error") } got, err := c.podsReadyForObject(t.Context(), tt.args.namespace, tt.args.obj) - if (err != nil) != tt.wantErr { - t.Errorf("podsReadyForObject() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("podsReadyForObject() got = %v, want %v", got, tt.want) + if tt.wantErr { + require.Error(t, err, "podsReadyForObject()") + } else { + require.NoError(t, err, "podsReadyForObject()") } + assert.Equal(t, tt.want, got, "podsReadyForObject() got = %v, want %v", got, tt.want) }) } } @@ -1097,9 +1048,7 @@ func Test_ReadyChecker_jobReady(t *testing.T) { t.Errorf("jobReady() error = %v, wantErr %v", err, tt.wantErr) return } - if got != tt.want { - t.Errorf("jobReady() = %v, want %v", got, tt.want) - } + assert.Equal(t, tt.want, got, "jobReady() = %v, want %v", got, tt.want) }) } } @@ -1131,9 +1080,8 @@ func Test_ReadyChecker_volumeReady(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := NewReadyChecker(fake.NewClientset()) - if got := c.volumeReady(tt.args.v); got != tt.want { - t.Errorf("volumeReady() = %v, want %v", got, tt.want) - } + got := c.volumeReady(tt.args.v) + assert.Equalf(t, tt.want, got, "volumeReady() = %v, want %v", got, tt.want) }) } } @@ -1177,9 +1125,7 @@ func Test_ReadyChecker_serviceReady(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := NewReadyChecker(fake.NewClientset()) got := c.serviceReady(tt.args.service) - if got != tt.want { - t.Errorf("serviceReady() = %v, want %v", got, tt.want) - } + assert.Equal(t, tt.want, got, "serviceReady() = %v, want %v", got, tt.want) }) } } @@ -1246,9 +1192,7 @@ func Test_ReadyChecker_crdBetaReady(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := NewReadyChecker(fake.NewClientset()) got := c.crdBetaReady(tt.args.crdBeta) - if got != tt.want { - t.Errorf("crdBetaReady() = %v, want %v", got, tt.want) - } + assert.Equal(t, tt.want, got, "crdBetaReady() = %v, want %v", got, tt.want) }) } } @@ -1315,9 +1259,7 @@ func Test_ReadyChecker_crdReady(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := NewReadyChecker(fake.NewClientset()) got := c.crdReady(tt.args.crdBeta) - if got != tt.want { - t.Errorf("crdBetaReady() = %v, want %v", got, tt.want) - } + assert.Equal(t, tt.want, got, "crdBetaReady() = %v, want %v", got, tt.want) }) } } From a3fa28ae22934450389ab31343c0ca20321d302c Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:56 +0200 Subject: [PATCH 468/571] chore(pkg): refactor: convert tests to testify assert/require part 12 refactor: convert tests to testify assert/require in pkg Signed-off-by: Matthieu MOREL --- pkg/action/install_test.go | 197 ++++++++++++------------------------ pkg/cmd/verify_test.go | 20 ++-- pkg/provenance/sign_test.go | 155 ++++++++-------------------- 3 files changed, 116 insertions(+), 256 deletions(-) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index f79590670..da680f41a 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -185,9 +185,7 @@ func TestInstallRelease(t *testing.T) { vals := map[string]any{} ctx, done := context.WithCancel(t.Context()) resi, err := instAction.RunWithContext(ctx, buildChart(), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") res, err := releaserToV1Release(resi) req.NoError(err) is.Equal("test-install-release", res.Name, "Expected release name.") @@ -238,9 +236,7 @@ func TestInstallReleaseWithTakeOwnership_ResourceNotOwned(t *testing.T) { instAction := installActionWithConfig(config) instAction.TakeOwnership = true resi, err := instAction.Run(buildChart(), nil) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") res, err := releaserToV1Release(resi) req.NoError(err) @@ -262,9 +258,7 @@ func TestInstallReleaseWithTakeOwnership_ResourceOwned(t *testing.T) { instAction := installActionWithConfig(config) instAction.TakeOwnership = false resi, err := instAction.Run(buildChart(), nil) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") res, err := releaserToV1Release(resi) req.NoError(err) r, err := instAction.cfg.Releases.Get(res.Name, res.Version) @@ -285,7 +279,7 @@ func TestInstallReleaseWithTakeOwnership_ResourceOwnedNoFlag(t *testing.T) { instAction := installActionWithConfig(config) _, err := instAction.Run(buildChart(), nil) req.Error(err) - is.Contains(err.Error(), "unable to continue with install") + is.ErrorContains(err, "unable to continue with install") } func TestInstallReleaseWithValues(t *testing.T) { @@ -303,9 +297,7 @@ func TestInstallReleaseWithValues(t *testing.T) { }, } resi, err := instAction.Run(buildChart(withSampleValues()), userVals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") res, err := releaserToV1Release(resi) req.NoError(err) is.Equal("test-install-release", res.Name, "Expected release name.") @@ -344,9 +336,7 @@ func TestInstallRelease_WithNotes(t *testing.T) { instAction.ReleaseName = "with-notes" vals := map[string]any{} resi, err := instAction.Run(buildChart(withNotes("note here")), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") res, err := releaserToV1Release(resi) req.NoError(err) @@ -376,9 +366,7 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) { instAction.ReleaseName = "with-notes" vals := map[string]any{} resi, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") res, err := releaserToV1Release(resi) req.NoError(err) @@ -400,9 +388,7 @@ func TestInstallRelease_WithChartAndDependencyParentNotes(t *testing.T) { instAction.ReleaseName = "with-notes" vals := map[string]any{} resi, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") res, err := releaserToV1Release(resi) req.NoError(err) @@ -424,9 +410,7 @@ func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) { instAction.SubNotes = true vals := map[string]any{} resi, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") res, err := releaserToV1Release(resi) req.NoError(err) @@ -451,9 +435,7 @@ func TestInstallRelease_DryRunClient(t *testing.T) { vals := map[string]any{} resi, err := instAction.Run(buildChart(withSampleTemplates()), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") res, err := releaserToV1Release(resi) req.NoError(err) @@ -466,7 +448,7 @@ func TestInstallRelease_DryRunClient(t *testing.T) { _, err = instAction.cfg.Releases.Get(res.Name, res.Version) req.Error(err) is.Len(res.Hooks, 1) - is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "expect hook to not be marked as run") + is.Zero(res.Hooks[0].LastRun.CompletedAt, "expect hook to not be marked as run") is.Equal("Dry run complete", res.Info.Description) } } @@ -480,9 +462,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { instAction.DryRunStrategy = DryRunClient vals := map[string]any{} resi, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") res, err := releaserToV1Release(resi) req.NoError(err) is.Contains(res.Manifest, "---\n# Source: hello/templates/secret.yaml\napiVersion: v1\nkind: Secret") @@ -495,9 +475,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { instAction.HideSecret = true vals = map[string]any{} res2i, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") res2, err := releaserToV1Release(res2i) req.NoError(err) @@ -511,9 +489,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { instAction.DryRunStrategy = DryRunNone vals = map[string]any{} _, err = instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) - if err == nil { - t.Fatal("Did not get the expected error when dry-run is false and hide secret is true") - } + req.Error(err, "Did not get the expected error when dry-run is false and hide secret is true") } // Regression test for #7955 @@ -532,9 +508,7 @@ func TestInstallRelease_DryRun_Lookup(t *testing.T) { }) resi, err := instAction.Run(mockChart, vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") res, err := releaserToV1Release(resi) req.NoError(err) @@ -543,6 +517,7 @@ func TestInstallRelease_DryRun_Lookup(t *testing.T) { func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.DryRunStrategy = DryRunNone vals := map[string]any{} @@ -550,10 +525,8 @@ func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { expectedErr := `hello/templates/incorrect:1:10 executing "hello/templates/incorrect" at <.Values.bad.doh>: nil pointer evaluating interface {}.doh` - if err == nil { - t.Fatalf("Install should fail containing error: %s", expectedErr) - } - is.Contains(err.Error(), expectedErr) + req.Error(err, "Install should fail containing error: %s", expectedErr) + is.ErrorContains(err, expectedErr) } func TestInstallRelease_NoHooks(t *testing.T) { @@ -562,17 +535,15 @@ func TestInstallRelease_NoHooks(t *testing.T) { instAction := installAction(t) instAction.DisableHooks = true instAction.ReleaseName = "no-hooks" - require.NoError(t, instAction.cfg.Releases.Create(releaseStub())) + req.NoError(instAction.cfg.Releases.Create(releaseStub())) vals := map[string]any{} resi, err := instAction.Run(buildChart(), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") res, err := releaserToV1Release(resi) req.NoError(err) - is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "hooks should not run with no-hooks") + is.Zero(res.Hooks[0].LastRun.CompletedAt, "hooks should not run with no-hooks") } func TestInstallRelease_FailedHooks(t *testing.T) { @@ -637,7 +608,7 @@ func TestInstallRelease_KubeVersion(t *testing.T) { vals = map[string]any{} _, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals) req.Error(err) - is.Contains(err.Error(), "chart requires kubeVersion: >=99.0.0 which is incompatible with Kubernetes v1.20.") + is.ErrorContains(err, "chart requires kubeVersion: >=99.0.0 which is incompatible with Kubernetes v1.20.") } func TestInstallRelease_Wait(t *testing.T) { @@ -680,7 +651,7 @@ func TestInstallRelease_Wait_Interrupted(t *testing.T) { _, err := instAction.RunWithContext(ctx, buildChart(), vals) req.Error(err) - is.Contains(err.Error(), "context canceled") + req.ErrorContains(err, "context canceled") is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background time.Sleep(10 * time.Second) // wait for goroutine to finish @@ -723,8 +694,8 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) { resi, err := instAction.Run(buildChart(), vals) req.Error(err) - is.Contains(err.Error(), "I timed out") - is.Contains(err.Error(), "rollback-on-failure") + req.ErrorContains(err, "I timed out") + req.ErrorContains(err, "rollback-on-failure") res, err := releaserToV1Release(resi) req.NoError(err) @@ -748,9 +719,9 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) { _, err := instAction.Run(buildChart(), vals) req.Error(err) - is.Contains(err.Error(), "I timed out") - is.Contains(err.Error(), "uninstall fail") - is.Contains(err.Error(), "an error occurred while uninstalling the release") + req.ErrorContains(err, "I timed out") + req.ErrorContains(err, "uninstall fail") + is.ErrorContains(err, "an error occurred while uninstalling the release") }) } func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { @@ -771,9 +742,9 @@ func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { resi, err := instAction.RunWithContext(ctx, buildChart(), vals) req.Error(err) - is.Contains(err.Error(), "context canceled") - is.Contains(err.Error(), "rollback-on-failure") - is.Contains(err.Error(), "uninstalled") + req.ErrorContains(err, "context canceled") + req.ErrorContains(err, "rollback-on-failure") + req.ErrorContains(err, "uninstalled") res, err := releaserToV1Release(resi) req.NoError(err) @@ -820,36 +791,23 @@ func TestNameTemplate(t *testing.T) { } for _, tc := range testCases { - n, err := TemplateName(tc.tpl) - if err != nil { + t.Run(tc.tpl, func(t *testing.T) { + n, err := TemplateName(tc.tpl) if tc.expectedErrorStr == "" { - t.Errorf("Was not expecting error, but got: %v", err) - continue + require.NoError(t, err) + } else { + require.Error(t, err) + re, compErr := regexp.Compile(tc.expectedErrorStr) + require.NoError(t, compErr, "Expected error string failed to compile") + assert.True(t, re.MatchString(err.Error()), "Error didn't match for %s expected %s", tc.tpl, tc.expectedErrorStr) } - re, compErr := regexp.Compile(tc.expectedErrorStr) - if compErr != nil { - t.Errorf("Expected error string failed to compile: %v", compErr) - continue - } - if !re.MatchString(err.Error()) { - t.Errorf("Error didn't match for %s expected %s but got %v", tc.tpl, tc.expectedErrorStr, err) - continue - } - } - if err == nil && tc.expectedErrorStr != "" { - t.Errorf("Was expecting error %s but didn't get an error back", tc.expectedErrorStr) - } - if tc.expected != "" { - re, err := regexp.Compile(tc.expected) - if err != nil { - t.Errorf("Expected string failed to compile: %v", err) - continue - } - if !re.MatchString(n) { - t.Errorf("Returned name didn't match for %s expected %s but got %s", tc.tpl, tc.expected, n) + if tc.expected != "" { + re, err := regexp.Compile(tc.expected) + require.NoError(t, err) + assert.True(t, re.MatchString(n), "Returned name didn't match for %s expected %s but got %s", tc.tpl, tc.expected, n) } - } + }) } } @@ -864,9 +822,7 @@ func TestInstallReleaseOutputDir(t *testing.T) { instAction.OutputDir = dir _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") _, err = os.Stat(filepath.Join(dir, "hello", "templates", "goodbye")) req.NoError(err) @@ -901,9 +857,7 @@ func TestInstallOutputDirWithReleaseName(t *testing.T) { newDir := filepath.Join(dir, instAction.ReleaseName) _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") _, err = os.Stat(filepath.Join(newDir, "hello", "templates", "goodbye")) req.NoError(err) @@ -925,46 +879,37 @@ func TestInstallOutputDirWithReleaseName(t *testing.T) { func TestNameAndChart(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) chartName := "./foo" name, chrt, err := instAction.NameAndChart([]string{chartName}) - if err != nil { - t.Fatal(err) - } + req.NoError(err) is.Equal(instAction.ReleaseName, name) is.Equal(chartName, chrt) instAction.GenerateName = true _, _, err = instAction.NameAndChart([]string{"foo", chartName}) - if err == nil { - t.Fatal("expected an error") - } - is.Equal("cannot set --generate-name and also specify a name", err.Error()) + req.Error(err, "expected an error") + req.EqualError(err, "cannot set --generate-name and also specify a name") instAction.GenerateName = false instAction.NameTemplate = "{{ . }}" _, _, err = instAction.NameAndChart([]string{"foo", chartName}) - if err == nil { - t.Fatal("expected an error") - } - is.Equal("cannot set --name-template and also specify a name", err.Error()) + req.Error(err, "expected an error") + req.EqualError(err, "cannot set --name-template and also specify a name") instAction.NameTemplate = "" instAction.ReleaseName = "" _, _, err = instAction.NameAndChart([]string{chartName}) - if err == nil { - t.Fatal("expected an error") - } - is.Equal("must either provide a name or specify --generate-name", err.Error()) + req.Error(err, "expected an error") + req.EqualError(err, "must either provide a name or specify --generate-name") instAction.NameTemplate = "" instAction.ReleaseName = "" _, _, err = instAction.NameAndChart([]string{"foo", chartName, "bar"}) - if err == nil { - t.Fatal("expected an error") - } - is.Equal("expected at most two arguments, unexpected arguments: bar", err.Error()) + req.Error(err, "expected an error") + is.EqualError(err, "expected at most two arguments, unexpected arguments: bar") } func TestNameAndChartGenerateName(t *testing.T) { @@ -1016,9 +961,7 @@ func TestNameAndChartGenerateName(t *testing.T) { t.Parallel() name, chrt, err := instAction.NameAndChart([]string{tc.Chart}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) is.Equal(tc.ExpectedName, name) is.Equal(tc.Chart, chrt) @@ -1035,9 +978,7 @@ func TestInstallWithLabels(t *testing.T) { "key2": "val2", } resi, err := instAction.Run(buildChart(), nil) - if err != nil { - t.Fatalf("Failed install: %s", err) - } + req.NoError(err, "Failed install") res, err := releaserToV1Release(resi) req.NoError(err) @@ -1046,17 +987,15 @@ func TestInstallWithLabels(t *testing.T) { func TestInstallWithSystemLabels(t *testing.T) { is := assert.New(t) + req := require.New(t) instAction := installAction(t) instAction.Labels = map[string]string{ "owner": "val1", "key2": "val2", } _, err := instAction.Run(buildChart(), nil) - if err == nil { - t.Fatal("expected an error") - } - - is.Equal(fmt.Errorf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()), err) + req.Error(err, "expected an error") + is.EqualError(err, fmt.Sprintf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels())) } func TestUrlEqual(t *testing.T) { @@ -1111,13 +1050,9 @@ func TestUrlEqual(t *testing.T) { t.Parallel() u1, err := url.Parse(tc.url1) - if err != nil { - t.Fatalf("Failed to parse URL1 %s: %v", tc.url1, err) - } + require.NoError(t, err, "Failed to parse URL1 %s", tc.url1) u2, err := url.Parse(tc.url2) - if err != nil { - t.Fatalf("Failed to parse URL2 %s: %v", tc.url2, err) - } + require.NoError(t, err, "Failed to parse URL2 %s", tc.url2) is.Equal(tc.expected, urlEqual(u1, u2)) }) @@ -1285,11 +1220,7 @@ func TestInstallCRDs_CheckNilErrors(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { instAction := installAction(t) - - err := instAction.installCRDs(tt.input) - if err == nil { - t.Error("got nil expected err") - } + assert.Error(t, instAction.installCRDs(tt.input), "got nil expected err") }) } } diff --git a/pkg/cmd/verify_test.go b/pkg/cmd/verify_test.go index 050d799c9..0b4078e1e 100644 --- a/pkg/cmd/verify_test.go +++ b/pkg/cmd/verify_test.go @@ -19,6 +19,9 @@ import ( "fmt" "runtime" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestVerifyCmd(t *testing.T) { @@ -73,18 +76,11 @@ func TestVerifyCmd(t *testing.T) { t.Run(tt.name, func(t *testing.T) { _, out, err := executeActionCommand(tt.cmd) if tt.wantError { - if err == nil { - t.Errorf("Expected error, but got none: %q", out) - } - if err.Error() != tt.expect { - t.Errorf("Expected error %q, got %q", tt.expect, err) - } - return - } else if err != nil { - t.Errorf("Unexpected error: %s", err) - } - if out != tt.expect { - t.Errorf("Expected %q, got %q", tt.expect, out) + require.Error(t, err, "Expected error, but got none: %q", out) + assert.EqualError(t, err, tt.expect, "Expected error %q", tt.expect) + } else { + require.NoError(t, err) + assert.Equal(t, tt.expect, out, "Expected %q, got %q", tt.expect, out) } }) } diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go index 79b33a970..32253df90 100644 --- a/pkg/provenance/sign_test.go +++ b/pkg/provenance/sign_test.go @@ -89,14 +89,10 @@ func loadChartMetadataForSigning(t *testing.T, chartPath string) []byte { t.Helper() chart, err := loader.LoadFile(chartPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) metadataBytes, err := yaml.Marshal(chart.Metadata) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return metadataBytes } @@ -106,170 +102,116 @@ func TestMessageBlock(t *testing.T) { // Read the chart file data archiveData, err := os.ReadFile(testChartfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) out, err := messageBlock(archiveData, filepath.Base(testChartfile), metadataBytes) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) got := out.String() - if got != testMessageBlock { - t.Errorf("Expected:\n%q\nGot\n%q\n", testMessageBlock, got) - } + assert.Equal(t, testMessageBlock, got, "Expected:\n%q\nGot\n%q\n", testMessageBlock, got) } func TestParseMessageBlock(t *testing.T) { sc, err := parseMessageBlock([]byte(testMessageBlock)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // parseMessageBlock only returns checksums, not metadata (like upstream) - if lsc := len(sc.Files); lsc != 1 { - t.Errorf("Expected 1 file, got %d", lsc) - } + lsc := len(sc.Files) + assert.Equalf(t, 1, lsc, "Expected 1 file, got %d", lsc) - if hash, ok := sc.Files["hashtest-1.2.3.tgz"]; !ok { - t.Error("hashtest file not found in Files") - } else if hash != "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888" { - t.Errorf("Unexpected hash: %q", hash) - } + hash, ok := sc.Files["hashtest-1.2.3.tgz"] + assert.True(t, ok, "hashtest file not found in Files") + assert.Equalf(t, "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888", hash, "Unexpected hash: %q", hash) } func TestLoadKey(t *testing.T) { k, err := loadKey(testKeyfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if _, ok := k.Identities[testKeyName]; !ok { - t.Errorf("Expected to load a key for user %q", testKeyName) - } + _, ok := k.Identities[testKeyName] + assert.Truef(t, ok, "Expected to load a key for user %q", testKeyName) } func TestLoadKeyRing(t *testing.T) { k, err := loadKeyRing(testPubfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if len(k) > 1 { - t.Errorf("Expected 1, got %d", len(k)) - } + assert.LessOrEqualf(t, len(k), 1, "Expected 1, got %d", len(k)) for _, e := range k { - if ii, ok := e.Identities[testKeyName]; !ok { - t.Errorf("Expected %s in %v", testKeyName, ii) - } + ii, ok := e.Identities[testKeyName] + assert.Truef(t, ok, "Expected %s in %v", testKeyName, ii) } } func TestDigest(t *testing.T) { f, err := os.Open(testChartfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer f.Close() hash, err := Digest(f) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) sig, err := readSumFile(testSumfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if !strings.Contains(sig, hash) { - t.Errorf("Expected %s to be in %s", hash, sig) - } + assert.Contains(t, sig, hash, "Expected %s to be in %s", hash, sig) } func TestNewFromFiles(t *testing.T) { s, err := NewFromFiles(testKeyfile, testPubfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if _, ok := s.Entity.Identities[testKeyName]; !ok { - t.Errorf("Expected to load a key for user %q", testKeyName) - } + _, ok := s.Entity.Identities[testKeyName] + assert.Truef(t, ok, "Expected to load a key for user %q", testKeyName) } func TestDigestFile(t *testing.T) { hash, err := DigestFile(testChartfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) sig, err := readSumFile(testSumfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if !strings.Contains(sig, hash) { - t.Errorf("Expected %s to be in %s", hash, sig) - } + assert.Contains(t, sig, hash, "Expected %s to be in %s", hash, sig) } func TestDecryptKey(t *testing.T) { k, err := NewFromKeyring(testPasswordKeyfile, testPasswordKeyName) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if !k.Entity.PrivateKey.Encrypted { - t.Fatal("Key is not encrypted") - } + require.True(t, k.Entity.PrivateKey.Encrypted, "Key is not encrypted") // We give this a simple callback that returns the password. - if err := k.DecryptKey(func(_ string) ([]byte, error) { + require.NoError(t, k.DecryptKey(func(_ string) ([]byte, error) { return []byte("secret"), nil - }); err != nil { - t.Fatal(err) - } + })) // Re-read the key (since we already unlocked it) k, err = NewFromKeyring(testPasswordKeyfile, testPasswordKeyName) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Now we give it a bogus password. - if err := k.DecryptKey(func(_ string) ([]byte, error) { + require.Error(t, k.DecryptKey(func(_ string) ([]byte, error) { return []byte("secrets_and_lies"), nil - }); err == nil { - t.Fatal("Expected an error when giving a bogus passphrase") - } + }), "Expected an error when giving a bogus passphrase") } func TestClearSign(t *testing.T) { signer, err := NewFromFiles(testKeyfile, testPubfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) metadataBytes := loadChartMetadataForSigning(t, testChartfile) // Read the chart file data archiveData, err := os.ReadFile(testChartfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) sig, err := signer.ClearSign(archiveData, filepath.Base(testChartfile), metadataBytes) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) t.Logf("Sig:\n%s", sig) - if !strings.Contains(sig, testMessageBlock) { - t.Errorf("expected message block to be in sig: %s", sig) - } + assert.Contains(t, sig, testMessageBlock, "expected message block to be in sig: %s", sig) } func TestMixedKeyringRSASigningAndVerification(t *testing.T) { @@ -335,9 +277,7 @@ func (s failSigner) Sign(_ io.Reader, _ []byte, _ crypto.SignerOpts) ([]byte, er func TestClearSignError(t *testing.T) { signer, err := NewFromFiles(testKeyfile, testPubfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // ensure that signing always fails signer.Entity.PrivateKey.PrivateKey = failSigner{} @@ -346,18 +286,11 @@ func TestClearSignError(t *testing.T) { // Read the chart file data archiveData, err := os.ReadFile(testChartfile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) sig, err := signer.ClearSign(archiveData, filepath.Base(testChartfile), metadataBytes) - if err == nil { - t.Fatal("didn't get an error from ClearSign but expected one") - } - - if sig != "" { - t.Fatalf("expected an empty signature after failed ClearSign but got %q", sig) - } + require.Error(t, err, "didn't get an error from ClearSign but expected one") + assert.Empty(t, sig, "expected an empty signature after failed ClearSign but got %q", sig) } func TestVerify(t *testing.T) { @@ -386,7 +319,7 @@ func TestVerify(t *testing.T) { require.Errorf(t, err, "Expected %s to fail.", testTamperedSigBlock) var sErr pgperrors.SignatureError - if assert.ErrorAs(t, err, &sErr, "Expected invalid signature error, got %q (%T)", err, err) { + if assert.ErrorAs(t, err, &sErr, "Expected invalid signature error") { t.Logf("Tampered sig block error: %s (%T)", sErr, sErr) } } From aae82bf97a4c02c70982c14118912c459db43ec9 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:57 +0200 Subject: [PATCH 469/571] chore(pkg): refactor: convert tests to testify assert/require part 13 refactor: convert tests to testify assert/require in pkg Signed-off-by: Matthieu MOREL --- pkg/action/pull_test.go | 4 +- pkg/chart/v2/lint/rules/chartfile_test.go | 161 +++++----------------- pkg/cmd/search/search_test.go | 75 ++++------ pkg/pusher/ocipusher_test.go | 160 ++++++--------------- 4 files changed, 100 insertions(+), 300 deletions(-) diff --git a/pkg/action/pull_test.go b/pkg/action/pull_test.go index 7cd5d3e85..a483de248 100644 --- a/pkg/action/pull_test.go +++ b/pkg/action/pull_test.go @@ -48,9 +48,7 @@ func TestPullSetRegistryClient(t *testing.T) { func TestPullRun_ChartNotFound(t *testing.T) { srv, err := startLocalServerForTests(t, nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer srv.Close() config := actionConfigFixture(t) diff --git a/pkg/chart/v2/lint/rules/chartfile_test.go b/pkg/chart/v2/lint/rules/chartfile_test.go index c9e202770..7c9f1c422 100644 --- a/pkg/chart/v2/lint/rules/chartfile_test.go +++ b/pkg/chart/v2/lint/rules/chartfile_test.go @@ -20,9 +20,11 @@ import ( "errors" "os" "path/filepath" - "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/chart/v2/lint/support" chartutil "helm.sh/helm/v4/pkg/chart/v2/util" @@ -47,35 +49,17 @@ var badChartName, _ = chartutil.LoadChartfile(badChartNamePath) func TestValidateChartYamlNotDirectory(t *testing.T) { _ = os.Mkdir(nonExistingChartFilePath, os.ModePerm) defer os.Remove(nonExistingChartFilePath) - - err := validateChartYamlNotDirectory(nonExistingChartFilePath) - if err == nil { - t.Error("validateChartYamlNotDirectory to return a linter error, got no error") - } + assert.Error(t, validateChartYamlNotDirectory(nonExistingChartFilePath), "validateChartYamlNotDirectory to return a linter error, got no error") } func TestValidateChartYamlFormat(t *testing.T) { - err := validateChartYamlFormat(errors.New("Read error")) - if err == nil { - t.Error("validateChartYamlFormat to return a linter error, got no error") - } - - err = validateChartYamlFormat(nil) - if err != nil { - t.Error("validateChartYamlFormat to return no error, got a linter error") - } + require.Error(t, validateChartYamlFormat(errors.New("Read error")), "validateChartYamlFormat to return a linter error, got no error") + assert.NoError(t, validateChartYamlFormat(nil), "validateChartYamlFormat to return no error, got a linter error") } func TestValidateChartName(t *testing.T) { - err := validateChartName(badChart) - if err == nil { - t.Error("validateChartName to return a linter error, got no error") - } - - err = validateChartName(badChartName) - if err == nil { - t.Error("expected validateChartName to return a linter error for an invalid name, got no error") - } + require.Error(t, validateChartName(badChart), "validateChartName to return a linter error, got no error") + assert.Error(t, validateChartName(badChartName), "expected validateChartName to return a linter error for an invalid name, got no error") } func TestValidateChartVersion(t *testing.T) { @@ -93,18 +77,12 @@ func TestValidateChartVersion(t *testing.T) { for _, test := range failTest { badChart.Version = test.Version - err := validateChartVersion(badChart) - if err == nil || !strings.Contains(err.Error(), test.ErrorMsg) { - t.Errorf("validateChartVersion(%s) to return \"%s\", got no error", test.Version, test.ErrorMsg) - } + require.ErrorContainsf(t, validateChartVersion(badChart), test.ErrorMsg, "validateChartVersion(%s) to return \"%s\", got no error", test.Version, test.ErrorMsg) } for _, version := range successTest { badChart.Version = version - err := validateChartVersion(badChart) - if err != nil { - t.Errorf("validateChartVersion(%s) to return no error, got a linter error", version) - } + assert.NoError(t, validateChartVersion(badChart), "validateChartVersion(%s) to return no error, got a linter error", version) } } @@ -122,18 +100,12 @@ func TestValidateChartVersionStrictSemVerV2(t *testing.T) { for _, test := range failTest { badChart.Version = test.Version - err := validateChartVersionStrictSemVerV2(badChart) - if err == nil || !strings.Contains(err.Error(), test.ErrorMsg) { - t.Errorf("validateChartVersionStrictSemVerV2(%s) to return \"%s\", got no error", test.Version, test.ErrorMsg) - } + require.ErrorContainsf(t, validateChartVersionStrictSemVerV2(badChart), test.ErrorMsg, "validateChartVersionStrictSemVerV2(%s) to return \"%s\", got no error", test.Version, test.ErrorMsg) } for _, version := range successTest { badChart.Version = version - err := validateChartVersionStrictSemVerV2(badChart) - if err != nil { - t.Errorf("validateChartVersionStrictSemVerV2(%s) to return no error, got a linter error", version) - } + assert.NoError(t, validateChartVersionStrictSemVerV2(badChart), "validateChartVersionStrictSemVerV2(%s) to return no error, got a linter error", version) } } @@ -158,29 +130,17 @@ func TestValidateChartMaintainer(t *testing.T) { for _, test := range failTest { badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}} - err := validateChartMaintainer(badChart) - if err == nil || !strings.Contains(err.Error(), test.ErrorMsg) { - t.Errorf("validateChartMaintainer(%s, %s) to return \"%s\", got no error", test.Name, test.Email, test.ErrorMsg) - } + require.ErrorContainsf(t, validateChartMaintainer(badChart), test.ErrorMsg, "validateChartMaintainer(%s, %s) to return \"%s\", got no error", test.Name, test.Email, test.ErrorMsg) } for _, test := range successTest { badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}} - err := validateChartMaintainer(badChart) - if err != nil { - t.Errorf("validateChartMaintainer(%s, %s) to return no error, got %s", test.Name, test.Email, err.Error()) - } + require.NoError(t, validateChartMaintainer(badChart), "validateChartMaintainer(%s, %s)", test.Name, test.Email) } // Testing for an empty maintainer badChart.Maintainers = []*chart.Maintainer{nil} - err := validateChartMaintainer(badChart) - if err == nil { - t.Error("validateChartMaintainer did not return error for nil maintainer as expected") - } - if err.Error() != "a maintainer entry is empty" { - t.Errorf("validateChartMaintainer returned unexpected error for nil maintainer: %s", err.Error()) - } + assert.EqualError(t, validateChartMaintainer(badChart), "a maintainer entry is empty") } func TestValidateChartSources(t *testing.T) { @@ -188,18 +148,12 @@ func TestValidateChartSources(t *testing.T) { var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"} for _, test := range failTest { badChart.Sources = []string{test} - err := validateChartSources(badChart) - if err == nil || !strings.Contains(err.Error(), "invalid source URL") { - t.Errorf("validateChartSources(%s) to return \"invalid source URL\", got no error", test) - } + require.ErrorContainsf(t, validateChartSources(badChart), "invalid source URL", "validateChartSources(%s) to return \"invalid source URL\", got no error", test) } for _, test := range successTest { badChart.Sources = []string{test} - err := validateChartSources(badChart) - if err != nil { - t.Errorf("validateChartSources(%s) to return no error, got %s", test, err.Error()) - } + assert.NoError(t, validateChartSources(badChart), "validateChartSources(%s) to return no error", test) } } @@ -209,24 +163,13 @@ func TestValidateChartIconPresence(t *testing.T) { Icon: "", } - err := validateChartIconPresence(testChart) - - if err == nil { - t.Error("validateChartIconPresence to return a linter error, got no error") - } else if !strings.Contains(err.Error(), "icon is recommended") { - t.Errorf("expected %q, got %q", "icon is recommended", err.Error()) - } + assert.ErrorContainsf(t, validateChartIconPresence(testChart), "icon is recommended", "expected %q", "icon is recommended") }) t.Run("Icon present", func(t *testing.T) { testChart := &chart.Metadata{ Icon: "http://example.org/icon.png", } - - err := validateChartIconPresence(testChart) - - if err != nil { - t.Errorf("Unexpected error: %q", err.Error()) - } + assert.NoError(t, validateChartIconPresence(testChart)) }) } @@ -235,18 +178,12 @@ func TestValidateChartIconURL(t *testing.T) { var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish.png"} for _, test := range failTest { badChart.Icon = test - err := validateChartIconURL(badChart) - if err == nil || !strings.Contains(err.Error(), "invalid icon URL") { - t.Errorf("validateChartIconURL(%s) to return \"invalid icon URL\", got no error", test) - } + require.ErrorContainsf(t, validateChartIconURL(badChart), "invalid icon URL", "validateChartIconURL(%s) to return \"invalid icon URL\", got no error", test) } for _, test := range successTest { badChart.Icon = test - err := validateChartSources(badChart) - if err != nil { - t.Errorf("validateChartIconURL(%s) to return no error, got %s", test, err.Error()) - } + assert.NoError(t, validateChartSources(badChart), "validateChartIconURL(%s) to return no error", test) } } @@ -257,37 +194,15 @@ func TestChartfile(t *testing.T) { msgs := linter.Messages expectedNumberOfErrorMessages := 7 - if len(msgs) != expectedNumberOfErrorMessages { - t.Errorf("Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs)) - return - } - - if !strings.Contains(msgs[0].Err.Error(), "name is required") { - t.Errorf("Unexpected message 0: %s", msgs[0].Err) - } - - if !strings.Contains(msgs[1].Err.Error(), "apiVersion is required. The value must be either \"v1\" or \"v2\"") { - t.Errorf("Unexpected message 1: %s", msgs[1].Err) - } - - if !strings.Contains(msgs[2].Err.Error(), "version '0.0.0.0' is not a valid SemVer") { - t.Errorf("Unexpected message 2: %s", msgs[2].Err) - } - - if !strings.Contains(msgs[3].Err.Error(), "icon is recommended") { - t.Errorf("Unexpected message 3: %s", msgs[3].Err) - } - - if !strings.Contains(msgs[4].Err.Error(), "chart type is not valid in apiVersion") { - t.Errorf("Unexpected message 4: %s", msgs[4].Err) - } + require.Lenf(t, msgs, expectedNumberOfErrorMessages, "Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs)) - if !strings.Contains(msgs[5].Err.Error(), "dependencies are not valid in the Chart file with apiVersion") { - t.Errorf("Unexpected message 5: %s", msgs[5].Err) - } - if !strings.Contains(msgs[6].Err.Error(), "version '0.0.0.0' is not a valid SemVerV2") { - t.Errorf("Unexpected message 6: %s", msgs[6].Err) - } + require.ErrorContains(t, msgs[0].Err, "name is required", "Unexpected message 0: %s", msgs[0].Err) + require.ErrorContains(t, msgs[1].Err, "apiVersion is required. The value must be either \"v1\" or \"v2\"", "Unexpected message 1: %s", msgs[1].Err) + require.ErrorContains(t, msgs[2].Err, "version '0.0.0.0' is not a valid SemVer", "Unexpected message 2: %s", msgs[2].Err) + require.ErrorContains(t, msgs[3].Err, "icon is recommended", "Unexpected message 3: %s", msgs[3].Err) + require.ErrorContains(t, msgs[4].Err, "chart type is not valid in apiVersion", "Unexpected message 4: %s", msgs[4].Err) + require.ErrorContains(t, msgs[5].Err, "dependencies are not valid in the Chart file with apiVersion", "Unexpected message 5: %s", msgs[5].Err) + assert.ErrorContains(t, msgs[6].Err, "version '0.0.0.0' is not a valid SemVerV2", "Unexpected message 6: %s", msgs[6].Err) }) t.Run("Chart.yaml validity issues due to type mismatch", func(t *testing.T) { @@ -301,19 +216,9 @@ func TestChartfile(t *testing.T) { return } - if !strings.Contains(msgs[0].Err.Error(), "version should be of type string") { - t.Errorf("Unexpected message 0: %s", msgs[0].Err) - } - - if !strings.Contains(msgs[1].Err.Error(), "version '7.2445e+06' is not a valid SemVer") { - t.Errorf("Unexpected message 1: %s", msgs[1].Err) - } - - if !strings.Contains(msgs[2].Err.Error(), "appVersion should be of type string") { - t.Errorf("Unexpected message 2: %s", msgs[2].Err) - } - if !strings.Contains(msgs[3].Err.Error(), "version '7.2445e+06' is not a valid SemVerV2") { - t.Errorf("Unexpected message 3: %s", msgs[3].Err) - } + require.ErrorContains(t, msgs[0].Err, "version should be of type string", "Unexpected message 0: %s", msgs[0].Err) + require.ErrorContains(t, msgs[1].Err, "version '7.2445e+06' is not a valid SemVer", "Unexpected message 1: %s", msgs[1].Err) + require.ErrorContains(t, msgs[2].Err, "appVersion should be of type string", "Unexpected message 2: %s", msgs[2].Err) + assert.ErrorContains(t, msgs[3].Err, "version '7.2445e+06' is not a valid SemVerV2", "Unexpected message 3: %s", msgs[3].Err) }) } diff --git a/pkg/cmd/search/search_test.go b/pkg/cmd/search/search_test.go index 46394e77f..f8c9504d1 100644 --- a/pkg/cmd/search/search_test.go +++ b/pkg/cmd/search/search_test.go @@ -20,6 +20,9 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/repo/v1" ) @@ -40,24 +43,16 @@ func TestSortScore(t *testing.T) { // Test Score for i := range expectScore { - if expectScore[i] != in[i].Score { - t.Errorf("Sort error on index %d: expected %d, got %d", i, expectScore[i], in[i].Score) - } + assert.Equalf(t, expectScore[i], in[i].Score, "Sort error on index %d: expected %d, got %d", i, expectScore[i], in[i].Score) } // Test Name for i := range expect { - if expect[i] != in[i].Name { - t.Errorf("Sort error: expected %s, got %s", expect[i], in[i].Name) - } + assert.Equalf(t, expect[i], in[i].Name, "Sort error: expected %s, got %s", expect[i], in[i].Name) } // Test version of last two items - if in[5].Chart.Version != "1.2.4" { - t.Errorf("Expected 1.2.4, got %s", in[5].Chart.Version) - } - if in[6].Chart.Version != "1.2.3" { - t.Error("Expected 1.2.3 to be last") - } + assert.Equalf(t, "1.2.4", in[5].Chart.Version, "Expected 1.2.4, got %s", in[5].Chart.Version) + assert.Equal(t, "1.2.3", in[6].Chart.Version, "Expected 1.2.3 to be last") } var indexfileEntries = map[string]repo.ChartVersions{ @@ -122,30 +117,22 @@ func loadTestIndex(_ *testing.T, all bool) *Index { func TestAll(t *testing.T) { i := loadTestIndex(t, false) all := i.All() - if len(all) != 4 { - t.Errorf("Expected 4 entries, got %d", len(all)) - } + assert.Lenf(t, all, 4, "Expected 4 entries, got %d", len(all)) i = loadTestIndex(t, true) all = i.All() - if len(all) != 5 { - t.Errorf("Expected 5 entries, got %d", len(all)) - } + assert.Lenf(t, all, 5, "Expected 5 entries, got %d", len(all)) } func TestAddRepo_Sort(t *testing.T) { i := loadTestIndex(t, true) sr, err := i.Search("TESTING/SANTA-MARIA", 100, false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) SortScore(sr) ch := sr[0] expect := "1.2.3" - if ch.Chart.Version != expect { - t.Errorf("Expected %q, got %q", expect, ch.Chart.Version) - } + assert.Equalf(t, ch.Chart.Version, expect, "Expected %q, got %q", expect, ch.Chart.Version) } func TestSearchByName(t *testing.T) { @@ -247,9 +234,7 @@ func TestSearchByName(t *testing.T) { charts, err := i.Search(tt.query, 100, tt.regexp) if err != nil { if tt.fail { - if !strings.Contains(err.Error(), tt.failMsg) { - t.Fatalf("Unexpected error message: %s", err) - } + require.ErrorContains(t, err, tt.failMsg) return } t.Fatalf("%s: %s", tt.name, err) @@ -258,9 +243,7 @@ func TestSearchByName(t *testing.T) { SortScore(charts) l := len(charts) - if l != len(tt.expect) { - t.Fatalf("Expected %d result, got %d", len(tt.expect), l) - } + require.Lenf(t, tt.expect, l, "Expected %d result, got %d", len(tt.expect), l) // For empty result sets, just keep going. if l == 0 { return @@ -268,9 +251,7 @@ func TestSearchByName(t *testing.T) { for i, got := range charts { ex := tt.expect[i] - if got.Name != ex.Name { - t.Errorf("[%d]: Expected name %q, got %q", i, ex.Name, got.Name) - } + assert.Equalf(t, got.Name, ex.Name, "[%d]: Expected name %q, got %q", i, ex.Name, got.Name) } }) } @@ -280,12 +261,8 @@ func TestSearchByNameAll(t *testing.T) { // Test with the All bit turned on. i := loadTestIndex(t, true) cs, err := i.Search("santa-maria", 100, false) - if err != nil { - t.Fatal(err) - } - if len(cs) != 2 { - t.Errorf("expected 2 charts, got %d", len(cs)) - } + require.NoError(t, err) + assert.Lenf(t, cs, 2, "expected 2 charts, got %d", len(cs)) } func TestCalcScore(t *testing.T) { @@ -293,16 +270,12 @@ func TestCalcScore(t *testing.T) { fields := []string{"aaa", "bbb", "ccc", "ddd"} matchline := strings.Join(fields, sep) - if r := i.calcScore(2, matchline); r != 0 { - t.Errorf("Expected 0, got %d", r) - } - if r := i.calcScore(5, matchline); r != 1 { - t.Errorf("Expected 1, got %d", r) - } - if r := i.calcScore(10, matchline); r != 2 { - t.Errorf("Expected 2, got %d", r) - } - if r := i.calcScore(14, matchline); r != 3 { - t.Errorf("Expected 3, got %d", r) - } + r := i.calcScore(2, matchline) + assert.Equalf(t, 0, r, "Expected 0, got %d", r) + r = i.calcScore(5, matchline) + assert.Equalf(t, 1, r, "Expected 1, got %d", r) + r = i.calcScore(10, matchline) + assert.Equalf(t, 2, r, "Expected 2, got %d", r) + r = i.calcScore(14, matchline) + assert.Equalf(t, 3, r, "Expected 3, got %d", r) } diff --git a/pkg/pusher/ocipusher_test.go b/pkg/pusher/ocipusher_test.go index a4542500a..ceb61d442 100644 --- a/pkg/pusher/ocipusher_test.go +++ b/pkg/pusher/ocipusher_test.go @@ -21,9 +21,9 @@ import ( "io" "os" "path/filepath" - "strings" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/registry" @@ -31,13 +31,10 @@ import ( func TestNewOCIPusher(t *testing.T) { p, err := NewOCIPusher() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if _, ok := p.(*OCIPusher); !ok { - t.Fatal("Expected NewOCIPusher to produce an *OCIPusher") - } + _, ok := p.(*OCIPusher) + require.True(t, ok, "Expected NewOCIPusher to produce an *OCIPusher") cd := "../../testdata" join := filepath.Join @@ -51,55 +48,28 @@ func TestNewOCIPusher(t *testing.T) { WithInsecureSkipTLSVerify(insecureSkipTLSVerify), WithPlainHTTP(plainHTTP), ) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) op, ok := p.(*OCIPusher) - if !ok { - t.Fatal("Expected NewOCIPusher to produce an *OCIPusher") - } - - if op.opts.certFile != pub { - t.Errorf("Expected NewOCIPusher to contain %q as the public key file, got %q", pub, op.opts.certFile) - } - - if op.opts.keyFile != priv { - t.Errorf("Expected NewOCIPusher to contain %q as the private key file, got %q", priv, op.opts.keyFile) - } - - if op.opts.caFile != ca { - t.Errorf("Expected NewOCIPusher to contain %q as the CA file, got %q", ca, op.opts.caFile) - } - - if op.opts.plainHTTP != plainHTTP { - t.Errorf("Expected NewOCIPusher to have plainHTTP as %t, got %t", plainHTTP, op.opts.plainHTTP) - } - - if op.opts.insecureSkipTLSVerify != insecureSkipTLSVerify { - t.Errorf("Expected NewOCIPusher to have insecureSkipVerifyTLS as %t, got %t", insecureSkipTLSVerify, op.opts.insecureSkipTLSVerify) - } + require.True(t, ok, "Expected NewOCIPusher to produce an *OCIPusher") + assert.Equal(t, pub, op.opts.certFile, "Expected NewOCIPusher to contain %q as the public key file, got %q", pub, op.opts.certFile) + assert.Equal(t, priv, op.opts.keyFile, "Expected NewOCIPusher to contain %q as the private key file, got %q", priv, op.opts.keyFile) + assert.Equal(t, ca, op.opts.caFile, "Expected NewOCIPusher to contain %q as the CA file, got %q", ca, op.opts.caFile) + assert.Equal(t, plainHTTP, op.opts.plainHTTP, "Expected NewOCIPusher to have plainHTTP as %t, got %t", plainHTTP, op.opts.plainHTTP) + assert.Equal(t, insecureSkipTLSVerify, op.opts.insecureSkipTLSVerify, "Expected NewOCIPusher to have insecureSkipVerifyTLS as %t, got %t", insecureSkipTLSVerify, op.opts.insecureSkipTLSVerify) // Test if setting registryClient is being passed to the ops registryClient, err := registry.NewClient() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) p, err = NewOCIPusher( WithRegistryClient(registryClient), ) - if err != nil { - t.Fatal(err) - } - op, ok = p.(*OCIPusher) - if !ok { - t.Fatal("expected NewOCIPusher to produce an *OCIPusher") - } + require.NoError(t, err) - if op.opts.registryClient != registryClient { - t.Errorf("Expected NewOCIPusher to contain %p as RegistryClient, got %p", registryClient, op.opts.registryClient) - } + op, ok = p.(*OCIPusher) + require.True(t, ok, "expected NewOCIPusher to produce an *OCIPusher") + assert.Equal(t, registryClient, op.opts.registryClient, "Expected NewOCIPusher to contain %p as RegistryClient, got %p", registryClient, op.opts.registryClient) } func TestOCIPusher_Push_ErrorHandling(t *testing.T) { @@ -127,23 +97,13 @@ func TestOCIPusher_Push_ErrorHandling(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pusher, err := NewOCIPusher() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) chartRef := tt.chartRef if tt.setupFunc != nil { chartRef = tt.setupFunc() } - - err = pusher.Push(chartRef, "oci://localhost:5000/test") - if err == nil { - t.Fatal("Expected error but got none") - } - - if !strings.Contains(err.Error(), tt.expectedError) { - t.Errorf("Expected error containing %q, got %q", tt.expectedError, err.Error()) - } + assert.ErrorContains(t, pusher.Push(chartRef, "oci://localhost:5000/test"), tt.expectedError) }) } } @@ -227,30 +187,20 @@ func TestOCIPusher_newRegistryClient(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pusher, err := NewOCIPusher(tt.opts...) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) op, ok := pusher.(*OCIPusher) - if !ok { - t.Fatal("Expected *OCIPusher") - } + require.True(t, ok, "Expected *OCIPusher") client, err := op.newRegistryClient() if tt.expectError { - if err == nil { - t.Fatal("Expected error but got none") - } - if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) { - t.Errorf("Expected error containing %q, got %q", tt.errorContains, err.Error()) + require.Error(t, err, "Expected error but got none") + if tt.errorContains != "" { + require.ErrorContainsf(t, err, tt.errorContains, "Expected error containing %q, got %q", tt.errorContains, err.Error()) } } else { - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - if client == nil { - t.Fatal("Expected non-nil registry client") - } + require.NoError(t, err) + require.NotNil(t, client, "Expected non-nil registry client") } }) } @@ -287,25 +237,18 @@ func TestOCIPusher_Push_ChartOperations(t *testing.T) { // Copy a valid chart src, err := os.Open(chartPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer src.Close() dst, err := os.Create(tempChart) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if _, err := io.Copy(dst, src); err != nil { - t.Fatal(err) - } + _, err = io.Copy(dst, src) + require.NoError(t, err) dst.Close() // Make the file unreadable - if err := os.Chmod(tempChart, 0o000); err != nil { - t.Fatal(err) - } + require.NoError(t, os.Chmod(tempChart, 0o000)) return tempChart, func() { os.Chmod(tempChart, 0o644) // Restore permissions for cleanup @@ -328,25 +271,18 @@ func TestOCIPusher_Push_ChartOperations(t *testing.T) { // Copy chart file src, err := os.Open(chartWithProvPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer src.Close() dst, err := os.Create(tempChart) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if _, err := io.Copy(dst, src); err != nil { - t.Fatal(err) - } + _, err = io.Copy(dst, src) + require.NoError(t, err) dst.Close() // Create provenance file - if err := os.WriteFile(tempProv, []byte("test provenance data"), 0o644); err != nil { - t.Fatal(err) - } + require.NoError(t, os.WriteFile(tempProv, []byte("test provenance data"), 0o644)) return tempChart, func() {} }, @@ -373,9 +309,7 @@ func TestOCIPusher_Push_ChartOperations(t *testing.T) { } pusher, err := NewOCIPusher(tt.options...) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) err = pusher.Push(chartRef, tt.href) @@ -400,27 +334,17 @@ func TestOCIPusher_Push_MultipleOptions(t *testing.T) { } pusher, err := NewOCIPusher() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Test that multiple options are applied correctly - err = pusher.Push(chartPath, "oci://localhost:5000/test", + // We expect an error since we're not actually pushing to a registry + require.Error(t, pusher.Push(chartPath, "oci://localhost:5000/test", WithPlainHTTP(true), WithInsecureSkipTLSVerify(true), - ) - - // We expect an error since we're not actually pushing to a registry - if err == nil { - t.Fatal("Expected error when pushing without a valid registry") - } + ), "Expected error when pushing without a valid registry") // Verify options were applied op := pusher.(*OCIPusher) - if !op.opts.plainHTTP { - t.Error("Expected plainHTTP option to be applied") - } - if !op.opts.insecureSkipTLSVerify { - t.Error("Expected insecureSkipTLSVerify option to be applied") - } + assert.True(t, op.opts.plainHTTP, "Expected plainHTTP option to be applied") + assert.True(t, op.opts.insecureSkipTLSVerify, "Expected insecureSkipTLSVerify option to be applied") } From dc66ee7be786b1ac146a21fb2114b9678aaf25a4 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:56 +0200 Subject: [PATCH 470/571] chore(pkg): refactor: convert tests to testify assert/require part 14 refactor: convert tests to testify assert/require in pkg/storage/driver Signed-off-by: Matthieu MOREL --- pkg/chart/v2/metadata_test.go | 20 ++-- pkg/downloader/chart_downloader_test.go | 129 ++++++++---------------- pkg/storage/driver/secrets_test.go | 115 ++++++--------------- pkg/storage/driver/sql_test.go | 128 +++++++---------------- 4 files changed, 112 insertions(+), 280 deletions(-) diff --git a/pkg/chart/v2/metadata_test.go b/pkg/chart/v2/metadata_test.go index 63dd99e52..720ca4886 100644 --- a/pkg/chart/v2/metadata_test.go +++ b/pkg/chart/v2/metadata_test.go @@ -16,8 +16,10 @@ limitations under the License. package v2 import ( - "errors" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestValidate(t *testing.T) { @@ -192,21 +194,13 @@ func TestValidate(t *testing.T) { for _, tt := range tests { result := tt.md.Validate() - if !errors.Is(result, tt.err) { - t.Errorf("expected %q, got %q in test %q", tt.err, result, tt.name) - } + assert.ErrorIs(t, result, tt.err, "expected %q, got %q in test %q", tt.err, result, tt.name) } } func TestValidate_sanitize(t *testing.T) { md := &Metadata{APIVersion: "v2", Name: "test", Version: "1.0", Description: "\adescr\u0081iption\rtest", Maintainers: []*Maintainer{{Name: "\r"}}} - if err := md.Validate(); err != nil { - t.Fatalf("unexpected error: %s", err) - } - if md.Description != "description test" { - t.Fatalf("description was not sanitized: %q", md.Description) - } - if md.Maintainers[0].Name != " " { - t.Fatal("maintainer name was not sanitized") - } + require.NoError(t, md.Validate()) + require.Equal(t, "description test", md.Description, "description was not sanitized: %q", md.Description) + require.Equal(t, " ", md.Maintainers[0].Name, "maintainer name was not sanitized") } diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index a4de2f5e3..7187bdff1 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -18,7 +18,6 @@ package downloader import ( "crypto/sha256" "encoding/hex" - "errors" "os" "path/filepath" "testing" @@ -69,9 +68,7 @@ func TestResolveChartRef(t *testing.T) { // Create a mock registry client for OCI references registryClient, err := registry.NewClient() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) c := ChartDownloader{ Out: os.Stderr, @@ -93,9 +90,8 @@ func TestResolveChartRef(t *testing.T) { t.Errorf("%s: failed with error %q", tt.name, err) continue } - if got := u.String(); got != tt.expect { - t.Errorf("%s: expected %s, got %s", tt.name, tt.expect, got) - } + got := u.String() + assert.Equalf(t, tt.expect, got, "%s: expected %s, got %s", tt.name, tt.expect, got) } } @@ -162,14 +158,10 @@ func TestResolveChartOpts(t *testing.T) { func TestVerifyChart(t *testing.T) { v, err := VerifyChart("testdata/signtest-0.1.0.tgz", "testdata/signtest-0.1.0.tgz.prov", "testdata/helm-test-key.pub") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // The verification is tested at length in the provenance package. Here, // we just want a quick sanity check that the v is not empty. - if v.FileHash == "" { - t.Error("Digest missing") - } + assert.NotEmpty(t, v.FileHash, "Digest missing") } func TestIsTar(t *testing.T) { @@ -183,9 +175,7 @@ func TestIsTar(t *testing.T) { } for src, expect := range tests { - if isTar(src) != expect { - t.Errorf("%q should be %t", src, expect) - } + assert.Equal(t, expect, isTar(src), "%q should be %t", src, expect) } } @@ -196,13 +186,8 @@ func TestDownloadTo(t *testing.T) { repotest.WithMiddleware(repotest.BasicAuthMiddleware(t)), ) defer srv.Stop() - if err := srv.CreateIndex(); err != nil { - t.Fatal(err) - } - - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.CreateIndex()) + require.NoError(t, srv.LinkIndices()) contentCache := t.TempDir() @@ -226,21 +211,14 @@ func TestDownloadTo(t *testing.T) { cname := "/signtest-0.1.0.tgz" dest := srv.Root() where, v, err := c.DownloadTo(srv.URL()+cname, "", dest) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if expect := filepath.Join(dest, cname); where != expect { - t.Errorf("Expected download to %s, got %s", expect, where) - } - - if v.FileHash == "" { - t.Error("File hash was empty, but verification is required.") - } + expect := filepath.Join(dest, cname) + assert.Equalf(t, expect, where, "Expected download to %s, got %s", expect, where) + assert.NotEmpty(t, v.FileHash, "File hash was empty, but verification is required.") - if _, err := os.Stat(filepath.Join(dest, cname)); err != nil { - t.Error(err) - } + _, err = os.Stat(filepath.Join(dest, cname)) + assert.NoError(t, err) } func TestDownloadTo_TLS(t *testing.T) { @@ -251,12 +229,8 @@ func TestDownloadTo_TLS(t *testing.T) { repotest.WithTLSConfig(repotest.MakeTestTLSConfig(t, "../../testdata")), ) defer srv.Stop() - if err := srv.CreateIndex(); err != nil { - t.Fatal(err) - } - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.CreateIndex()) + require.NoError(t, srv.LinkIndices()) repoConfig := filepath.Join(srv.Root(), "repositories.yaml") repoCache := srv.Root() @@ -285,22 +259,15 @@ func TestDownloadTo_TLS(t *testing.T) { cname := "test/signtest" dest := srv.Root() where, v, err := c.DownloadTo(cname, "", dest) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) target := filepath.Join(dest, "signtest-0.1.0.tgz") - if expect := target; where != expect { - t.Errorf("Expected download to %s, got %s", expect, where) - } + expect := target + assert.Equalf(t, expect, where, "Expected download to %s, got %s", expect, where) + assert.NotEmpty(t, v.FileHash, "File hash was empty, but verification is required.") - if v.FileHash == "" { - t.Error("File hash was empty, but verification is required.") - } - - if _, err := os.Stat(target); err != nil { - t.Error(err) - } + _, err = os.Stat(target) + assert.NoError(t, err) } func TestDownloadTo_VerifyLater(t *testing.T) { @@ -314,9 +281,7 @@ func TestDownloadTo_VerifyLater(t *testing.T) { repotest.WithChartSourceGlob("testdata/*.tgz*"), ) defer srv.Stop() - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.LinkIndices()) contentCache := t.TempDir() c := ChartDownloader{ @@ -333,20 +298,16 @@ func TestDownloadTo_VerifyLater(t *testing.T) { } cname := "/signtest-0.1.0.tgz" where, _, err := c.DownloadTo(srv.URL()+cname, "", dest) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if expect := filepath.Join(dest, cname); where != expect { - t.Errorf("Expected download to %s, got %s", expect, where) - } + expect := filepath.Join(dest, cname) + assert.Equalf(t, expect, where, "Expected download to %s, got %s", expect, where) - if _, err := os.Stat(filepath.Join(dest, cname)); err != nil { - t.Fatal(err) - } - if _, err := os.Stat(filepath.Join(dest, cname+".prov")); err != nil { - t.Fatal(err) - } + _, err = os.Stat(filepath.Join(dest, cname)) + require.NoError(t, err) + + _, err = os.Stat(filepath.Join(dest, cname+".prov")) + require.NoError(t, err) } func TestScanReposForURL(t *testing.T) { @@ -363,24 +324,17 @@ func TestScanReposForURL(t *testing.T) { u := "http://example.com/alpine-0.2.0.tgz" rf, err := repo.LoadFile(repoConfig) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) entry, err := c.scanReposForURL(u, rf) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if entry.Name != "testing" { - t.Errorf("Unexpected repo %q for URL %q", entry.Name, u) - } + assert.Equal(t, "testing", entry.Name, "Unexpected repo %q for URL %q", entry.Name, u) // A lookup failure should produce an ErrNoOwnerRepo u = "https://no.such.repo/foo/bar-1.23.4.tgz" - if _, err = c.scanReposForURL(u, rf); !errors.Is(err, ErrNoOwnerRepo) { - t.Fatalf("expected ErrNoOwnerRepo, got %v", err) - } + _, err = c.scanReposForURL(u, rf) + require.ErrorIs(t, err, ErrNoOwnerRepo) } func TestDownloadToCache(t *testing.T) { @@ -388,12 +342,8 @@ func TestDownloadToCache(t *testing.T) { repotest.WithChartSourceGlob("testdata/*.tgz*"), ) defer srv.Stop() - if err := srv.CreateIndex(); err != nil { - t.Fatal(err) - } - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.CreateIndex()) + require.NoError(t, srv.LinkIndices()) // The repo file needs to point to our server. repoFile := filepath.Join(srv.Root(), "repositories.yaml") @@ -431,6 +381,7 @@ func TestDownloadToCache(t *testing.T) { // Check that it's in the cache digest, _, err := c.ResolveChartVersion("test/signtest", "0.1.0") require.NoError(t, err) + digestBytes, err := hex.DecodeString(digest) require.NoError(t, err) var digestArray [sha256.Size]byte @@ -472,6 +423,7 @@ func TestDownloadToCache(t *testing.T) { // Check that both chart and prov are in cache digest, _, err := c.ResolveChartVersion("test/signtest", "0.1.0") require.NoError(t, err) + digestBytes, err := hex.DecodeString(digest) require.NoError(t, err) var digestArray [sha256.Size]byte @@ -479,6 +431,7 @@ func TestDownloadToCache(t *testing.T) { _, err = c.Cache.Get(digestArray, CacheChart) require.NoError(t, err, "chart should be in cache") + _, err = c.Cache.Get(digestArray, CacheProv) require.NoError(t, err, "provenance file should be in cache") diff --git a/pkg/storage/driver/secrets_test.go b/pkg/storage/driver/secrets_test.go index a11ec4380..8cc3f390d 100644 --- a/pkg/storage/driver/secrets_test.go +++ b/pkg/storage/driver/secrets_test.go @@ -16,10 +16,11 @@ package driver import ( "encoding/base64" "encoding/json" - "errors" "reflect" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" "helm.sh/helm/v4/pkg/release" @@ -29,9 +30,7 @@ import ( func TestSecretName(t *testing.T) { c := newTestFixtureSecrets(t) - if c.Name() != SecretsDriverName { - t.Errorf("Expected name to be %q, got %q", SecretsDriverName, c.Name()) - } + assert.Equal(t, SecretsDriverName, c.Name(), "Expected name to be %q, got %q", SecretsDriverName, c.Name()) } func TestSecretGet(t *testing.T) { @@ -45,13 +44,9 @@ func TestSecretGet(t *testing.T) { // get release with key got, err := secrets.Get(key) - if err != nil { - t.Fatalf("Failed to get release: %s", err) - } + require.NoError(t, err, "Failed to get release") // compare fetched release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } + assert.Truef(t, reflect.DeepEqual(rel, got), "Expected {%v}, got {%v}", rel, got) } func TestUNcompressedSecretGet(t *testing.T) { @@ -63,13 +58,9 @@ func TestUNcompressedSecretGet(t *testing.T) { // Create a test fixture which contains an uncompressed release secret, err := newSecretsObject(key, rel, nil) - if err != nil { - t.Fatalf("Failed to create secret: %s", err) - } + require.NoError(t, err, "Failed to create secret") b, err := json.Marshal(rel) - if err != nil { - t.Fatalf("Failed to marshal release: %s", err) - } + require.NoError(t, err, "Failed to marshal release") secret.Data["release"] = []byte(base64.StdEncoding.EncodeToString(b)) var mock MockSecretsInterface mock.objects = map[string]*v1.Secret{key: secret} @@ -77,13 +68,9 @@ func TestUNcompressedSecretGet(t *testing.T) { // get release with key got, err := secrets.Get(key) - if err != nil { - t.Fatalf("Failed to get release: %s", err) - } + require.NoError(t, err, "Failed to get release") // compare fetched release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } + assert.Truef(t, reflect.DeepEqual(rel, got), "Expected {%v}, got {%v}", rel, got) } func TestSecretList(t *testing.T) { @@ -102,12 +89,8 @@ func TestSecretList(t *testing.T) { return rls.Info.Status == common.StatusUninstalled }) // check - if err != nil { - t.Errorf("Failed to list deleted: %s", err) - } - if len(del) != 2 { - t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del) - } + require.NoError(t, err, "Failed to list deleted") + assert.Len(t, del, 2, "Expected 2 deleted, got %d:\n%v\n", len(del), del) // list all deployed releases dpl, err := secrets.List(func(rel release.Releaser) bool { @@ -115,12 +98,8 @@ func TestSecretList(t *testing.T) { return rls.Info.Status == common.StatusDeployed }) // check - if err != nil { - t.Errorf("Failed to list deployed: %s", err) - } - if len(dpl) != 2 { - t.Errorf("Expected 2 deployed, got %d", len(dpl)) - } + require.NoError(t, err, "Failed to list deployed") + assert.Len(t, dpl, 2, "Expected 2 deployed, got %d", len(dpl)) // list all superseded releases ssd, err := secrets.List(func(rel release.Releaser) bool { @@ -128,22 +107,14 @@ func TestSecretList(t *testing.T) { return rls.Info.Status == common.StatusSuperseded }) // check - if err != nil { - t.Errorf("Failed to list superseded: %s", err) - } - if len(ssd) != 2 { - t.Errorf("Expected 2 superseded, got %d", len(ssd)) - } + require.NoError(t, err, "Failed to list superseded") + assert.Len(t, ssd, 2, "Expected 2 superseded, got %d", len(ssd)) // Check if release having both system and custom labels, this is needed to ensure that selector filtering would work. rls := convertReleaserToV1(t, ssd[0]) _, ok := rls.Labels["name"] - if !ok { - t.Fatalf("Expected 'name' label in results, actual %v", rls.Labels) - } + require.True(t, ok, "Expected 'name' label in results, actual %v", rls.Labels) _, ok = rls.Labels["key1"] - if !ok { - t.Fatalf("Expected 'key1' label in results, actual %v", rls.Labels) - } + require.True(t, ok, "Expected 'key1' label in results, actual %v", rls.Labels) } func TestSecretQuery(t *testing.T) { @@ -157,17 +128,11 @@ func TestSecretQuery(t *testing.T) { }...) rls, err := secrets.Query(map[string]string{"status": "deployed"}) - if err != nil { - t.Fatalf("Failed to query: %s", err) - } - if len(rls) != 2 { - t.Fatalf("Expected 2 results, actual %d", len(rls)) - } + require.NoError(t, err, "Failed to query") + require.Len(t, rls, 2, "Expected 2 results, actual %d", len(rls)) _, err = secrets.Query(map[string]string{"name": "notExist"}) - if !errors.Is(err, ErrReleaseNotFound) { - t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) - } + assert.ErrorIs(t, err, ErrReleaseNotFound) } func TestSecretCreate(t *testing.T) { @@ -180,20 +145,14 @@ func TestSecretCreate(t *testing.T) { rel := releaseStub(name, vers, namespace, common.StatusDeployed) // store the release in a secret - if err := secrets.Create(key, rel); err != nil { - t.Fatalf("Failed to create release with key %q: %s", key, err) - } + require.NoErrorf(t, secrets.Create(key, rel), "Failed to create release with key %q", key) // get the release back got, err := secrets.Get(key) - if err != nil { - t.Fatalf("Failed to get release with key %q: %s", key, err) - } + require.NoError(t, err, "Failed to get release with key %q", key) // compare created release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } + assert.Truef(t, reflect.DeepEqual(rel, got), "Expected {%v}, got {%v}", rel, got) } func TestSecretUpdate(t *testing.T) { @@ -209,21 +168,15 @@ func TestSecretUpdate(t *testing.T) { rel.Info.Status = common.StatusSuperseded // perform the update - if err := secrets.Update(key, rel); err != nil { - t.Fatalf("Failed to update release: %s", err) - } + require.NoErrorf(t, secrets.Update(key, rel), "Failed to update release") // fetch the updated release goti, err := secrets.Get(key) - if err != nil { - t.Fatalf("Failed to get release with key %q: %s", key, err) - } + require.NoError(t, err, "Failed to get release with key %q", key) got := convertReleaserToV1(t, goti) // check release has actually been updated by comparing modified fields - if rel.Info.Status != got.Info.Status { - t.Errorf("Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String()) - } + assert.Equal(t, got.Info.Status, rel.Info.Status, "Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String()) } func TestSecretDelete(t *testing.T) { @@ -237,20 +190,12 @@ func TestSecretDelete(t *testing.T) { // perform the delete on a non-existing release _, err := secrets.Delete("nonexistent") - if !errors.Is(err, ErrReleaseNotFound) { - t.Fatalf("Expected ErrReleaseNotFound, got: {%v}", err) - } + require.ErrorIs(t, err, ErrReleaseNotFound, "Expected ErrReleaseNotFound") // perform the delete rls, err := secrets.Delete(key) - if err != nil { - t.Fatalf("Failed to delete release with key %q: %s", key, err) - } - if !reflect.DeepEqual(rel, rls) { - t.Errorf("Expected {%v}, got {%v}", rel, rls) - } + require.NoError(t, err, "Failed to delete release with key %q", key) + assert.Truef(t, reflect.DeepEqual(rel, rls), "Expected {%v}, got {%v}", rel, rls) _, err = secrets.Get(key) - if !errors.Is(err, ErrReleaseNotFound) { - t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) - } + assert.ErrorIs(t, err, ErrReleaseNotFound) } diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go index 6044ce3c6..adcc780aa 100644 --- a/pkg/storage/driver/sql_test.go +++ b/pkg/storage/driver/sql_test.go @@ -24,6 +24,8 @@ import ( sqlmock "github.com/DATA-DOG/go-sqlmock" migrate "github.com/rubenv/sql-migrate" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/release" "helm.sh/helm/v4/pkg/release/common" @@ -59,9 +61,7 @@ func (recentUnixTimestampArgument) Match(value driver.Value) bool { func TestSQLName(t *testing.T) { sqlDriver, _ := newTestFixtureSQL(t) - if sqlDriver.Name() != SQLDriverName { - t.Errorf("Expected name to be %s, got %s", SQLDriverName, sqlDriver.Name()) - } + assert.Equal(t, SQLDriverName, sqlDriver.Name(), "Expected name to be %s, got %s", SQLDriverName, sqlDriver.Name()) } func TestSQLGet(t *testing.T) { @@ -97,17 +97,10 @@ func TestSQLGet(t *testing.T) { mockGetReleaseCustomLabels(mock, key, namespace, rel.Labels) got, err := sqlDriver.Get(key) - if err != nil { - t.Fatalf("Failed to get release: %v", err) - } + require.NoError(t, err, "Failed to get release") - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected release {%v}, got {%v}", rel, got) - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } + assert.Truef(t, reflect.DeepEqual(rel, got), "Expected release {%v}, got {%v}", rel, got) + assert.NoErrorf(t, mock.ExpectationsWereMet(), "sql expectations weren't met") } func TestSQLList(t *testing.T) { @@ -157,12 +150,8 @@ func TestSQLList(t *testing.T) { return rls.Info.Status == common.StatusUninstalled }) // check - if err != nil { - t.Errorf("Failed to list deleted: %v", err) - } - if len(del) != 2 { - t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del) - } + require.NoError(t, err, "Failed to list deleted") + assert.Len(t, del, 2, "Expected 2 deleted, got %d:\n%v\n", len(del), del) // list all deployed releases dpl, err := sqlDriver.List(func(rel release.Releaser) bool { @@ -170,12 +159,8 @@ func TestSQLList(t *testing.T) { return rls.Info.Status == common.StatusDeployed }) // check - if err != nil { - t.Errorf("Failed to list deployed: %v", err) - } - if len(dpl) != 2 { - t.Errorf("Expected 2 deployed, got %d:\n%v\n", len(dpl), dpl) - } + require.NoError(t, err, "Failed to list deployed") + assert.Len(t, dpl, 2, "Expected 2 deployed, got %d:\n%v\n", len(dpl), dpl) // list all superseded releases ssd, err := sqlDriver.List(func(rel release.Releaser) bool { @@ -183,27 +168,16 @@ func TestSQLList(t *testing.T) { return rls.Info.Status == common.StatusSuperseded }) // check - if err != nil { - t.Errorf("Failed to list superseded: %v", err) - } - if len(ssd) != 2 { - t.Errorf("Expected 2 superseded, got %d:\n%v\n", len(ssd), ssd) - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } + require.NoError(t, err, "Failed to list superseded") + assert.Len(t, ssd, 2, "Expected 2 superseded, got %d:\n%v\n", len(ssd), ssd) + require.NoErrorf(t, mock.ExpectationsWereMet(), "sql expectations weren't met") // Check if release having both system and custom labels, this is needed to ensure that selector filtering would work. rls := convertReleaserToV1(t, ssd[0]) _, ok := rls.Labels["name"] - if !ok { - t.Fatalf("Expected 'name' label in results, actual %v", rls.Labels) - } + require.True(t, ok, "Expected 'name' label in results, actual %v", rls.Labels) _, ok = rls.Labels["key1"] - if !ok { - t.Fatalf("Expected 'key1' label in results, actual %v", rls.Labels) - } + require.True(t, ok, "Expected 'key1' label in results, actual %v", rls.Labels) } func TestSqlCreate(t *testing.T) { @@ -254,13 +228,8 @@ func TestSqlCreate(t *testing.T) { } mock.ExpectCommit() - if err := sqlDriver.Create(key, rel); err != nil { - t.Fatalf("failed to create release with key %s: %v", key, err) - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } + require.NoErrorf(t, sqlDriver.Create(key, rel), "failed to create release with key %s", key) + assert.NoErrorf(t, mock.ExpectationsWereMet(), "sql expectations weren't met") } func TestSqlCreateAlreadyExists(t *testing.T) { @@ -315,13 +284,8 @@ func TestSqlCreateAlreadyExists(t *testing.T) { ).RowsWillBeClosed() mock.ExpectRollback() - if err := sqlDriver.Create(key, rel); err == nil { - t.Fatalf("failed to create release with key %s: %v", key, err) - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } + require.Errorf(t, sqlDriver.Create(key, rel), "failed to create release with key %s", key) + assert.NoErrorf(t, mock.ExpectationsWereMet(), "sql expectations weren't met") } func TestSqlUpdate(t *testing.T) { @@ -352,13 +316,8 @@ func TestSqlUpdate(t *testing.T) { WithArgs(body, rel.Name, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, recentUnixTimestamp(), key, namespace). WillReturnResult(sqlmock.NewResult(0, 1)) - if err := sqlDriver.Update(key, rel); err != nil { - t.Fatalf("failed to update release with key %s: %v", key, err) - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } + require.NoErrorf(t, sqlDriver.Update(key, rel), "failed to update release with key %s", key) + assert.NoErrorf(t, mock.ExpectationsWereMet(), "sql expectations weren't met") } func TestSqlQuery(t *testing.T) { @@ -448,31 +407,20 @@ func TestSqlQuery(t *testing.T) { mockGetReleaseCustomLabels(mock, "", deployedRelease.Namespace, deployedRelease.Labels) _, err := sqlDriver.Query(labelSetUnknown) - if err == nil { - t.Errorf("Expected error {%v}, got nil", ErrReleaseNotFound) - } else if !errors.Is(err, ErrReleaseNotFound) { - t.Fatalf("failed to query for unknown smug-pigeon release: %v", err) - } + require.Errorf(t, err, "Expected error {%v}, got nil", ErrReleaseNotFound) + require.ErrorIsf(t, err, ErrReleaseNotFound, "failed to query for unknown smug-pigeon release") results, err := sqlDriver.Query(labelSetDeployed) - if err != nil { - t.Fatalf("failed to query for deployed smug-pigeon release: %v", err) - } + require.NoError(t, err, "failed to query for deployed smug-pigeon release") for _, res := range results { - if !reflect.DeepEqual(res, deployedRelease) { - t.Errorf("Expected release {%v}, got {%v}", deployedRelease, res) - } + assert.Truef(t, reflect.DeepEqual(res, deployedRelease), "Expected release {%v}, got {%v}", deployedRelease, res) } results, err = sqlDriver.Query(labelSetAll) - if err != nil { - t.Fatalf("failed to query release history for smug-pigeon: %v", err) - } + require.NoError(t, err, "failed to query release history for smug-pigeon") - if len(results) != 2 { - t.Errorf("expected a resultset of size 2, got %d", len(results)) - } + assert.Len(t, results, 2, "expected a resultset of size 2, got %d", len(results)) for _, res := range results { if !reflect.DeepEqual(res, deployedRelease) && !reflect.DeepEqual(res, supersededRelease) { @@ -480,9 +428,7 @@ func TestSqlQuery(t *testing.T) { } } - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } + assert.NoErrorf(t, mock.ExpectationsWereMet(), "sql expectations weren't met") } func TestSqlDelete(t *testing.T) { @@ -544,16 +490,12 @@ func TestSqlDelete(t *testing.T) { mock.ExpectCommit() deletedRelease, err := sqlDriver.Delete(key) - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } - if err != nil { - t.Fatalf("failed to delete release with key %q: %v", key, err) - } + require.NoError(t, err) + err = mock.ExpectationsWereMet() + assert.NoErrorf(t, err, "sql expectations weren't met") + require.NoError(t, err, "failed to delete release with key %q", key) - if !reflect.DeepEqual(rel, deletedRelease) { - t.Errorf("Expected release {%v}, got {%v}", rel, deletedRelease) - } + assert.Truef(t, reflect.DeepEqual(rel, deletedRelease), "Expected release {%v}, got {%v}", rel, deletedRelease) } func mockGetReleaseCustomLabels(mock sqlmock.Sqlmock, key string, namespace string, labels map[string]string) { @@ -621,8 +563,6 @@ func TestSqlCheckAppliedMigrations(t *testing.T) { ExpectQuery(""). WillReturnRows(rows) mock.ExpectCommit() - if sqlDriver.checkAlreadyApplied(c.migrationsToApply) != c.expectedResult { - t.Errorf("Test case: %v, Expected: %v, Have: %v, Explanation: %v", i, c.expectedResult, !c.expectedResult, c.errorExplanation) - } + assert.Equal(t, c.expectedResult, sqlDriver.checkAlreadyApplied(c.migrationsToApply), "Test case: %v, Expected: %v, Have: %v, Explanation: %v", i, c.expectedResult, !c.expectedResult, c.errorExplanation) } } From 71733656a5c64307f85290bc9fe6480ad637a117 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:55 +0200 Subject: [PATCH 471/571] chore(pkg): refactor: convert tests to testify assert/require part 15 #### Description refactor: convert tests to testify assert/require in pkg/cmd Signed-off-by: Matthieu MOREL --- pkg/chart/loader/load_test.go | 66 +++++++----------- pkg/cmd/dependency_update_test.go | 106 +++++++++------------------- pkg/cmd/repo_index_test.go | 112 +++++++++--------------------- pkg/getter/ocigetter_test.go | 92 +++++++----------------- 4 files changed, 116 insertions(+), 260 deletions(-) diff --git a/pkg/chart/loader/load_test.go b/pkg/chart/loader/load_test.go index a42861eac..3834fad7a 100644 --- a/pkg/chart/loader/load_test.go +++ b/pkg/chart/loader/load_test.go @@ -24,10 +24,12 @@ import ( "io" "maps" "path/filepath" - "strings" "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + c3 "helm.sh/helm/v4/internal/chart/v3" "helm.sh/helm/v4/pkg/chart" c2 "helm.sh/helm/v4/pkg/chart/v2" @@ -59,20 +61,13 @@ description: A test chart Size: int64(len(data)), ModTime: time.Now(), } - if err := tw.WriteHeader(header); err != nil { - t.Fatalf("Failed to write tar header for %s: %v", name, err) - } - if _, err := tw.Write(data); err != nil { - t.Fatalf("Failed to write tar data for %s: %v", name, err) - } + require.NoErrorf(t, tw.WriteHeader(header), "Failed to write tar header for %s", name) + _, err := tw.Write(data) + require.NoErrorf(t, err, "Failed to write tar data for %s", name) } - if err := tw.Close(); err != nil { - t.Fatalf("Failed to close tar writer: %v", err) - } - if err := gw.Close(); err != nil { - t.Fatalf("Failed to close gzip writer: %v", err) - } + require.NoErrorf(t, tw.Close(), "Failed to close tar writer") + require.NoErrorf(t, gw.Close(), "Failed to close gzip writer") return &buf } @@ -149,37 +144,26 @@ func TestLoadArchive(t *testing.T) { loadedChart, err := LoadArchive(reader) if tc.expectedError != "" { - if err == nil || !strings.Contains(err.Error(), tc.expectedError) { - t.Errorf("Expected error containing %q, but got %v", tc.expectedError, err) - } - return - } + require.Errorf(t, err, "Expected error containing %q, but got no error", tc.expectedError) + assert.ErrorContains(t, err, tc.expectedError) + } else { + require.NoError(t, err) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - lac, err := chart.NewAccessor(loadedChart) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - eac, err := chart.NewAccessor(tc.expectedChart) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } + lac, err := chart.NewAccessor(loadedChart) + require.NoError(t, err) - if lac.Name() != eac.Name() { - t.Errorf("Expected chart name %q, got %q", eac.Name(), lac.Name()) - } + eac, err := chart.NewAccessor(tc.expectedChart) + require.NoError(t, err) + assert.Equalf(t, lac.Name(), eac.Name(), "Expected chart name %q, got %q", eac.Name(), lac.Name()) - var loadedAPIVersion string - switch lc := loadedChart.(type) { - case *c2.Chart: - loadedAPIVersion = lc.Metadata.APIVersion - case *c3.Chart: - loadedAPIVersion = lc.Metadata.APIVersion - } - if loadedAPIVersion != tc.apiVersion { - t.Errorf("Expected API version %q, got %q", tc.apiVersion, loadedAPIVersion) + var loadedAPIVersion string + switch lc := loadedChart.(type) { + case *c2.Chart: + loadedAPIVersion = lc.Metadata.APIVersion + case *c3.Chart: + loadedAPIVersion = lc.Metadata.APIVersion + } + assert.Equalf(t, loadedAPIVersion, tc.apiVersion, "Expected API version %q, got %q", tc.apiVersion, loadedAPIVersion) } }) } diff --git a/pkg/cmd/dependency_update_test.go b/pkg/cmd/dependency_update_test.go index f9ff68097..13eb74532 100644 --- a/pkg/cmd/dependency_update_test.go +++ b/pkg/cmd/dependency_update_test.go @@ -16,14 +16,15 @@ limitations under the License. package cmd import ( - "errors" "fmt" "io/fs" "os" "path/filepath" - "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/internal/test/ensure" chart "helm.sh/helm/v4/pkg/chart/v2" chartutil "helm.sh/helm/v4/pkg/chart/v2/util" @@ -42,21 +43,16 @@ func TestDependencyUpdateCmd(t *testing.T) { t.Logf("Listening on directory %s", srv.Root()) ociSrv, err := repotest.NewOCIServer(t, srv.Root()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) contentCache := t.TempDir() ociChartName := "oci-depending-chart" c := createTestingMetadataForOCI(ociChartName, ociSrv.RegistryURL) - if _, err := chartutil.Save(c, ociSrv.Dir); err != nil { - t.Fatal(err) - } + _, err = chartutil.Save(c, ociSrv.Dir) + require.NoError(t, err) ociSrv.Run(t, repotest.WithDependingChart(c)) - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.LinkIndices()) dir := func(p ...string) string { return filepath.Join(append([]string{srv.Root()}, p...)...) @@ -65,9 +61,7 @@ func TestDependencyUpdateCmd(t *testing.T) { chartname := "depup" ch := createTestingMetadata(chartname, srv.URL()) md := ch.Metadata - if err := chartutil.SaveDir(ch, dir()); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(ch, dir())) _, out, err := executeActionCommand( fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --content-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir(), contentCache), @@ -78,30 +72,22 @@ func TestDependencyUpdateCmd(t *testing.T) { } // This is written directly to stdout, so we have to capture as is. - if !strings.Contains(out, `update from the "test" chart repository`) { - t.Errorf("Repo did not get updated\n%s", out) - } + assert.Contains(t, out, `update from the "test" chart repository`, "Repo did not get updated\n%s", out) // Make sure the actual file got downloaded. expect := dir(chartname, "charts/reqtest-0.1.0.tgz") - if _, err := os.Stat(expect); err != nil { - t.Fatal(err) - } + _, err = os.Stat(expect) + require.NoError(t, err) hash, err := provenance.DigestFile(expect) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) i, err := repo.LoadIndexFile(dir(helmpath.CacheIndexFile("test"))) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) reqver := i.Entries["reqtest"][0] - if h := reqver.Digest; h != hash { - t.Errorf("Failed hash match: expected %s, got %s", hash, h) - } + h := reqver.Digest + assert.Equalf(t, h, hash, "Failed hash match: expected %s, got %s", hash, h) // Now change the dependencies and update. This verifies that on update, // old dependencies are cleansed and new dependencies are added. @@ -109,9 +95,7 @@ func TestDependencyUpdateCmd(t *testing.T) { {Name: "reqtest", Version: "0.1.0", Repository: srv.URL()}, {Name: "compressedchart", Version: "0.3.0", Repository: srv.URL()}, } - if err := chartutil.SaveChartfile(dir(chartname, "Chart.yaml"), md); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveChartfile(dir(chartname, "Chart.yaml"), md)) _, out, err = executeActionCommand(fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --content-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir(), contentCache)) if err != nil { @@ -122,18 +106,14 @@ func TestDependencyUpdateCmd(t *testing.T) { // In this second run, we should see compressedchart-0.3.0.tgz, and not // the 0.1.0 version. expect = dir(chartname, "charts/compressedchart-0.3.0.tgz") - if _, err := os.Stat(expect); err != nil { - t.Fatalf("Expected %q: %s", expect, err) - } + _, err = os.Stat(expect) + require.NoErrorf(t, err, "Expected %q", expect) unexpected := dir(chartname, "charts/compressedchart-0.1.0.tgz") - if _, err := os.Stat(unexpected); err == nil { - t.Fatalf("Unexpected %q", unexpected) - } + _, err = os.Stat(unexpected) + require.Errorf(t, err, "Unexpected %q", unexpected) // test for OCI charts - if err := chartutil.SaveDir(c, dir()); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(c, dir())) cmd := fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json --content-cache %s --plain-http", dir(ociChartName), dir("repositories.yaml"), @@ -146,9 +126,8 @@ func TestDependencyUpdateCmd(t *testing.T) { t.Fatal(err) } expect = dir(ociChartName, "charts/oci-dependent-chart-0.1.0.tgz") - if _, err := os.Stat(expect); err != nil { - t.Fatal(err) - } + _, err = os.Stat(expect) + require.NoError(t, err) } func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) { @@ -162,9 +141,7 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) { defer srv.Stop() t.Logf("Listening on directory %s", srv.Root()) - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.LinkIndices()) chartname := "depupdelete" @@ -191,25 +168,18 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) { // Make sure charts dir still has dependencies files, err := os.ReadDir(filepath.Join(dir(chartname), "charts")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) dependencies := []string{"compressedchart-0.1.0.tgz", "reqtest-0.1.0.tgz"} - if len(dependencies) != len(files) { - t.Fatalf("Expected %d chart dependencies, got %d", len(dependencies), len(files)) - } + require.Len(t, dependencies, len(files), "Expected %d chart dependencies, got %d", len(dependencies), len(files)) for index, file := range files { - if dependencies[index] != file.Name() { - t.Fatalf("Chart dependency %s not matching %s", dependencies[index], file.Name()) - } + require.Equal(t, file.Name(), dependencies[index], "Chart dependency %s not matching %s", dependencies[index], file.Name()) } // Make sure tmpcharts-x is deleted tmpPath := filepath.Join(dir(chartname), fmt.Sprintf("tmpcharts-%d", os.Getpid())) - if _, err := os.Stat(tmpPath); !errors.Is(err, fs.ErrNotExist) { - t.Fatal("tmpcharts dir still exists") - } + _, err = os.Stat(tmpPath) + require.ErrorIs(t, err, fs.ErrNotExist, "tmpcharts dir still exists") } func TestDependencyUpdateCmd_WithRepoThatWasNotAdded(t *testing.T) { @@ -231,9 +201,7 @@ func TestDependencyUpdateCmd_WithRepoThatWasNotAdded(t *testing.T) { } ch.Metadata.Dependencies = append(ch.Metadata.Dependencies, chartDependency) - if err := chartutil.SaveDir(ch, dir()); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(ch, dir())) contentCache := t.TempDir() @@ -248,10 +216,8 @@ func TestDependencyUpdateCmd_WithRepoThatWasNotAdded(t *testing.T) { } // This is written directly to stdout, so we have to capture as is - if !strings.Contains(out, `Getting updates for unmanaged Helm repositories...`) { - t.Errorf("No ‘unmanaged’ Helm repo used in test chartdependency or it doesn’t cause the creation "+ - "of an ‘ad hoc’ repo index cache file\n%s", out) - } + assert.Contains(t, out, `Getting updates for unmanaged Helm repositories...`, "No ‘unmanaged’ Helm repo used in test chartdependency or it doesn’t cause the creation "+ + "of an ‘ad hoc’ repo index cache file\n%s", out) } func setupMockRepoServer(t *testing.T) *repotest.Server { @@ -263,9 +229,7 @@ func setupMockRepoServer(t *testing.T) *repotest.Server { t.Logf("Listening on directory %s", srv.Root()) - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.LinkIndices()) return srv } @@ -306,7 +270,5 @@ func createTestingMetadataForOCI(name, registryURL string) *chart.Chart { func createTestingChart(t *testing.T, dest, name, baseURL string) { t.Helper() cfile := createTestingMetadata(name, baseURL) - if err := chartutil.SaveDir(cfile, dest); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(cfile, dest)) } diff --git a/pkg/cmd/repo_index_test.go b/pkg/cmd/repo_index_test.go index 68f105d6d..30700749b 100644 --- a/pkg/cmd/repo_index_test.go +++ b/pkg/cmd/repo_index_test.go @@ -24,6 +24,9 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/repo/v1" ) @@ -31,134 +34,81 @@ func TestRepoIndexCmd(t *testing.T) { dir := t.TempDir() comp := filepath.Join(dir, "compressedchart-0.1.0.tgz") - if err := linkOrCopy("testdata/testcharts/compressedchart-0.1.0.tgz", comp); err != nil { - t.Fatal(err) - } + require.NoError(t, linkOrCopy("testdata/testcharts/compressedchart-0.1.0.tgz", comp)) comp2 := filepath.Join(dir, "compressedchart-0.2.0.tgz") - if err := linkOrCopy("testdata/testcharts/compressedchart-0.2.0.tgz", comp2); err != nil { - t.Fatal(err) - } + require.NoError(t, linkOrCopy("testdata/testcharts/compressedchart-0.2.0.tgz", comp2)) buf := bytes.NewBuffer(nil) c := newRepoIndexCmd(buf) - if err := c.RunE(c, []string{dir}); err != nil { - t.Error(err) - } + require.NoError(t, c.RunE(c, []string{dir})) destIndex := filepath.Join(dir, "index.yaml") index, err := repo.LoadIndexFile(destIndex) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if len(index.Entries) != 1 { - t.Errorf("expected 1 entry, got %d: %#v", len(index.Entries), index.Entries) - } + assert.Len(t, index.Entries, 1, "expected 1 entry, got %d: %#v", len(index.Entries), index.Entries) vs := index.Entries["compressedchart"] - if len(vs) != 2 { - t.Errorf("expected 2 versions, got %d: %#v", len(vs), vs) - } + assert.Len(t, vs, 2, "expected 2 versions, got %d: %#v", len(vs), vs) expectedVersion := "0.2.0" - if vs[0].Version != expectedVersion { - t.Errorf("expected %q, got %q", expectedVersion, vs[0].Version) - } + assert.Equal(t, expectedVersion, vs[0].Version, "expected %q, got %q", expectedVersion, vs[0].Version) b, err := os.ReadFile(destIndex) - if err != nil { - t.Fatal(err) - } - if json.Valid(b) { - t.Error("did not expect index file to be valid json") - } + require.NoError(t, err) + assert.False(t, json.Valid(b), "did not expect index file to be valid json") // Test with `--json` c.ParseFlags([]string{"--json", "true"}) - if err := c.RunE(c, []string{dir}); err != nil { - t.Error(err) - } + require.NoError(t, c.RunE(c, []string{dir})) - if b, err = os.ReadFile(destIndex); err != nil { - t.Fatal(err) - } - if !json.Valid(b) { - t.Error("index file is not valid json") - } + b, err = os.ReadFile(destIndex) + require.NoError(t, err) + assert.True(t, json.Valid(b), "index file is not valid json") // Test with `--merge` // Remove first two charts. - if err := os.Remove(comp); err != nil { - t.Fatal(err) - } - if err := os.Remove(comp2); err != nil { - t.Fatal(err) - } + require.NoError(t, os.Remove(comp)) + require.NoError(t, os.Remove(comp2)) // Add a new chart and a new version of an existing chart - if err := linkOrCopy("testdata/testcharts/reqtest-0.1.0.tgz", filepath.Join(dir, "reqtest-0.1.0.tgz")); err != nil { - t.Fatal(err) - } - if err := linkOrCopy("testdata/testcharts/compressedchart-0.3.0.tgz", filepath.Join(dir, "compressedchart-0.3.0.tgz")); err != nil { - t.Fatal(err) - } + require.NoError(t, linkOrCopy("testdata/testcharts/reqtest-0.1.0.tgz", filepath.Join(dir, "reqtest-0.1.0.tgz"))) + require.NoError(t, linkOrCopy("testdata/testcharts/compressedchart-0.3.0.tgz", filepath.Join(dir, "compressedchart-0.3.0.tgz"))) c.ParseFlags([]string{"--merge", destIndex}) - if err := c.RunE(c, []string{dir}); err != nil { - t.Error(err) - } + require.NoError(t, c.RunE(c, []string{dir})) index, err = repo.LoadIndexFile(destIndex) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if len(index.Entries) != 2 { - t.Errorf("expected 2 entries, got %d: %#v", len(index.Entries), index.Entries) - } + assert.Len(t, index.Entries, 2, "expected 2 entries, got %d: %#v", len(index.Entries), index.Entries) vs = index.Entries["compressedchart"] - if len(vs) != 3 { - t.Errorf("expected 3 versions, got %d: %#v", len(vs), vs) - } + assert.Len(t, vs, 3, "expected 3 versions, got %d: %#v", len(vs), vs) expectedVersion = "0.3.0" - if vs[0].Version != expectedVersion { - t.Errorf("expected %q, got %q", expectedVersion, vs[0].Version) - } + assert.Equal(t, expectedVersion, vs[0].Version, "expected %q, got %q", expectedVersion, vs[0].Version) // test that index.yaml gets generated on merge even when it doesn't exist - if err := os.Remove(destIndex); err != nil { - t.Fatal(err) - } + require.NoError(t, os.Remove(destIndex)) c.ParseFlags([]string{"--merge", destIndex}) - if err := c.RunE(c, []string{dir}); err != nil { - t.Error(err) - } + require.NoError(t, c.RunE(c, []string{dir})) index, err = repo.LoadIndexFile(destIndex) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // verify it didn't create an empty index.yaml and the merged happened - if len(index.Entries) != 2 { - t.Errorf("expected 2 entries, got %d: %#v", len(index.Entries), index.Entries) - } + assert.Len(t, index.Entries, 2, "expected 2 entries, got %d: %#v", len(index.Entries), index.Entries) vs = index.Entries["compressedchart"] - if len(vs) != 1 { - t.Errorf("expected 1 versions, got %d: %#v", len(vs), vs) - } + assert.Len(t, vs, 1, "expected 1 versions, got %d: %#v", len(vs), vs) expectedVersion = "0.3.0" - if vs[0].Version != expectedVersion { - t.Errorf("expected %q, got %q", expectedVersion, vs[0].Version) - } + assert.Equal(t, expectedVersion, vs[0].Version, "expected %q, got %q", expectedVersion, vs[0].Version) } func linkOrCopy(source, target string) error { diff --git a/pkg/getter/ocigetter_test.go b/pkg/getter/ocigetter_test.go index 5a7c99ebe..dfcc74890 100644 --- a/pkg/getter/ocigetter_test.go +++ b/pkg/getter/ocigetter_test.go @@ -21,18 +21,18 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/registry" ) func TestOCIGetter(t *testing.T) { g, err := NewOCIGetter(WithURL("oci://example.com")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if _, ok := g.(*OCIGetter); !ok { - t.Fatal("Expected NewOCIGetter to produce an *OCIGetter") - } + _, ok := g.(*OCIGetter) + require.True(t, ok, "Expected NewOCIGetter to produce an *OCIGetter") cd := "../../testdata" join := filepath.Join @@ -51,71 +51,41 @@ func TestOCIGetter(t *testing.T) { WithInsecureSkipVerifyTLS(insecureSkipVerifyTLS), WithPlainHTTP(plainHTTP), ) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) og, ok := g.(*OCIGetter) - if !ok { - t.Fatal("expected NewOCIGetter to produce an *OCIGetter") - } + require.True(t, ok, "expected NewOCIGetter to produce an *OCIGetter") - if og.opts.username != "I" { - t.Errorf("Expected NewOCIGetter to contain %q as the username, got %q", "I", og.opts.username) - } + assert.Equal(t, "I", og.opts.username, "Expected NewOCIGetter to contain %q as the username, got %q", "I", og.opts.username) - if og.opts.password != "Am" { - t.Errorf("Expected NewOCIGetter to contain %q as the password, got %q", "Am", og.opts.password) - } + assert.Equal(t, "Am", og.opts.password, "Expected NewOCIGetter to contain %q as the password, got %q", "Am", og.opts.password) - if og.opts.certFile != pub { - t.Errorf("Expected NewOCIGetter to contain %q as the public key file, got %q", pub, og.opts.certFile) - } + assert.Equal(t, pub, og.opts.certFile, "Expected NewOCIGetter to contain %q as the public key file, got %q", pub, og.opts.certFile) - if og.opts.keyFile != priv { - t.Errorf("Expected NewOCIGetter to contain %q as the private key file, got %q", priv, og.opts.keyFile) - } + assert.Equal(t, priv, og.opts.keyFile, "Expected NewOCIGetter to contain %q as the private key file, got %q", priv, og.opts.keyFile) - if og.opts.caFile != ca { - t.Errorf("Expected NewOCIGetter to contain %q as the CA file, got %q", ca, og.opts.caFile) - } + assert.Equal(t, ca, og.opts.caFile, "Expected NewOCIGetter to contain %q as the CA file, got %q", ca, og.opts.caFile) - if og.opts.timeout != timeout { - t.Errorf("Expected NewOCIGetter to contain %s as Timeout flag, got %s", timeout, og.opts.timeout) - } + assert.Equal(t, timeout, og.opts.timeout, "Expected NewOCIGetter to contain %s as Timeout flag, got %s", timeout, og.opts.timeout) - if og.opts.transport != transport { - t.Errorf("Expected NewOCIGetter to contain %p as Transport, got %p", transport, og.opts.transport) - } + assert.Equal(t, transport, og.opts.transport, "Expected NewOCIGetter to contain %p as Transport, got %p", transport, og.opts.transport) - if og.opts.plainHTTP != plainHTTP { - t.Errorf("Expected NewOCIGetter to have plainHTTP as %t, got %t", plainHTTP, og.opts.plainHTTP) - } + assert.Equal(t, plainHTTP, og.opts.plainHTTP, "Expected NewOCIGetter to have plainHTTP as %t, got %t", plainHTTP, og.opts.plainHTTP) - if og.opts.insecureSkipVerifyTLS != insecureSkipVerifyTLS { - t.Errorf("Expected NewOCIGetter to have insecureSkipVerifyTLS as %t, got %t", insecureSkipVerifyTLS, og.opts.insecureSkipVerifyTLS) - } + assert.Equal(t, insecureSkipVerifyTLS, og.opts.insecureSkipVerifyTLS, "Expected NewOCIGetter to have insecureSkipVerifyTLS as %t, got %t", insecureSkipVerifyTLS, og.opts.insecureSkipVerifyTLS) // Test if setting registryClient is being passed to the ops registryClient, err := registry.NewClient() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) g, err = NewOCIGetter( WithRegistryClient(registryClient), ) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) og, ok = g.(*OCIGetter) - if !ok { - t.Fatal("expected NewOCIGetter to produce an *OCIGetter") - } + require.True(t, ok, "expected NewOCIGetter to produce an *OCIGetter") - if og.opts.registryClient != registryClient { - t.Errorf("Expected NewOCIGetter to contain %p as RegistryClient, got %p", registryClient, og.opts.registryClient) - } + assert.Equal(t, registryClient, og.opts.registryClient, "Expected NewOCIGetter to contain %p as RegistryClient, got %p", registryClient, og.opts.registryClient) } func TestOCIHTTPTransportReuse(t *testing.T) { @@ -123,29 +93,19 @@ func TestOCIHTTPTransportReuse(t *testing.T) { _, err := g.newRegistryClient() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if g.transport == nil { - t.Fatal("Expected non nil value for transport") - } + require.NotNil(t, g.transport, "Expected non nil value for transport") transport1 := g.transport _, err = g.newRegistryClient() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if g.transport == nil { - t.Fatal("Expected non nil value for transport") - } + require.NotNil(t, g.transport, "Expected non nil value for transport") transport2 := g.transport - if transport1 != transport2 { - t.Fatal("Expected default transport to be reused") - } + require.Equal(t, transport2, transport1, "Expected default transport to be reused") } From 4073566e285d5bfaafaf0cec2b927fa037bfcb2a Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:55 +0200 Subject: [PATCH 472/571] chore(pkg): refactor: convert tests to testify assert/require part 17 #### Description refactor: convert tests to testify assert/require in pkg/cmd Signed-off-by: Matthieu MOREL --- pkg/action/uninstall_test.go | 4 +- pkg/chart/common/capabilities_test.go | 80 +++++++----------------- pkg/chart/v2/lint/rules/template_test.go | 71 +++++---------------- pkg/cmd/dependency_build_test.go | 67 +++++++------------- pkg/cmd/plugin_package_test.go | 75 +++++++--------------- pkg/cmd/repo_add_test.go | 76 ++++++---------------- 6 files changed, 108 insertions(+), 265 deletions(-) diff --git a/pkg/action/uninstall_test.go b/pkg/action/uninstall_test.go index 913ade1a5..f36fe7874 100644 --- a/pkg/action/uninstall_test.go +++ b/pkg/action/uninstall_test.go @@ -121,7 +121,7 @@ func TestUninstallRelease_Wait(t *testing.T) { unAction.cfg.KubeClient = failer resi, err := unAction.Run(rel.Name) req.Error(err) - is.Contains(err.Error(), "U timed out") + req.ErrorContains(err, "U timed out") res, err := releaserToV1Release(resi.Release) req.NoError(err) is.Equal(common.StatusUninstalled, res.Info.Status) @@ -163,7 +163,7 @@ func TestUninstallRelease_Cascade(t *testing.T) { unAction.cfg.KubeClient = failer _, err := unAction.Run(rel.Name) require.Error(t, err) - is.Contains(err.Error(), "failed to delete release: come-fail-away") + is.ErrorContains(err, "failed to delete release: come-fail-away") } func TestUninstallRun_UnreachableKubeClient(t *testing.T) { diff --git a/pkg/chart/common/capabilities_test.go b/pkg/chart/common/capabilities_test.go index c8d47581c..b099e3193 100644 --- a/pkg/chart/common/capabilities_test.go +++ b/pkg/chart/common/capabilities_test.go @@ -17,68 +17,44 @@ package common import ( "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestVersionSet(t *testing.T) { vs := VersionSet{"v1", "apps/v1"} - if d := len(vs); d != 2 { - t.Errorf("Expected 2 versions, got %d", d) - } + d := len(vs) + assert.Equalf(t, 2, d, "Expected 2 versions, got %d", d) - if !vs.Has("apps/v1") { - t.Error("Expected to find apps/v1") - } + assert.True(t, vs.Has("apps/v1"), "Expected to find apps/v1") - if vs.Has("Spanish/inquisition") { - t.Error("No one expects the Spanish/inquisition") - } + assert.False(t, vs.Has("Spanish/inquisition"), "No one expects the Spanish/inquisition") } func TestDefaultVersionSet(t *testing.T) { - if !DefaultVersionSet.Has("v1") { - t.Error("Expected core v1 version set") - } + assert.True(t, DefaultVersionSet.Has("v1"), "Expected core v1 version set") } func TestDefaultCapabilities(t *testing.T) { caps := DefaultCapabilities kv := caps.KubeVersion - if kv.String() != "v1.20.0" { - t.Errorf("Expected default KubeVersion.String() to be v1.20.0, got %q", kv.String()) - } - if kv.Version != "v1.20.0" { - t.Errorf("Expected default KubeVersion.Version to be v1.20.0, got %q", kv.Version) - } - if kv.GitVersion() != "v1.20.0" { - t.Errorf("Expected default KubeVersion.GitVersion() to be v1.20.0, got %q", kv.Version) - } - if kv.Major != "1" { - t.Errorf("Expected default KubeVersion.Major to be 1, got %q", kv.Major) - } - if kv.Minor != "20" { - t.Errorf("Expected default KubeVersion.Minor to be 20, got %q", kv.Minor) - } + assert.Equalf(t, "v1.20.0", kv.String(), "Expected default KubeVersion.String() to be v1.20.0, got %q", kv.String()) + assert.Equalf(t, "v1.20.0", kv.Version, "Expected default KubeVersion.Version to be v1.20.0, got %q", kv.Version) + assert.Equalf(t, "v1.20.0", kv.GitVersion(), "Expected default KubeVersion.GitVersion() to be v1.20.0, got %q", kv.Version) + assert.Equalf(t, "1", kv.Major, "Expected default KubeVersion.Major to be 1, got %q", kv.Major) + assert.Equalf(t, "20", kv.Minor, "Expected default KubeVersion.Minor to be 20, got %q", kv.Minor) hv := caps.HelmVersion - if hv.Version != "v4.2" { - t.Errorf("Expected default HelmVersion to be v4.2, got %q", hv.Version) - } + assert.Equalf(t, "v4.2", hv.Version, "Expected default HelmVersion to be v4.2, got %q", hv.Version) } func TestParseKubeVersion(t *testing.T) { kv, err := ParseKubeVersion("v1.16.0") - if err != nil { - t.Error("Expected v1.16.0 to parse successfully") - } - if kv.Version != "v1.16.0" { - t.Errorf("Expected parsed KubeVersion.Version to be v1.16.0, got %q", kv.String()) - } - if kv.Major != "1" { - t.Errorf("Expected parsed KubeVersion.Major to be 1, got %q", kv.Major) - } - if kv.Minor != "16" { - t.Errorf("Expected parsed KubeVersion.Minor to be 16, got %q", kv.Minor) - } + require.NoError(t, err, "Expected v1.16.0 to parse successfully") + assert.Equalf(t, "v1.16.0", kv.Version, "Expected parsed KubeVersion.Version to be v1.16.0, got %q", kv.String()) + assert.Equalf(t, "1", kv.Major, "Expected parsed KubeVersion.Major to be 1, got %q", kv.Major) + assert.Equalf(t, "16", kv.Minor, "Expected parsed KubeVersion.Minor to be 16, got %q", kv.Minor) } func TestParseKubeVersionWithVendorSuffixes(t *testing.T) { @@ -101,21 +77,11 @@ func TestParseKubeVersionWithVendorSuffixes(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { kv, err := ParseKubeVersion(tt.input) - if err != nil { - t.Fatalf("ParseKubeVersion() error = %v", err) - } - if kv.Version != tt.wantVer { - t.Errorf("Version = %q, want %q", kv.Version, tt.wantVer) - } - if kv.String() != tt.wantString { - t.Errorf("String() = %q, want %q", kv.String(), tt.wantString) - } - if kv.Major != tt.wantMajor { - t.Errorf("Major = %q, want %q", kv.Major, tt.wantMajor) - } - if kv.Minor != tt.wantMinor { - t.Errorf("Minor = %q, want %q", kv.Minor, tt.wantMinor) - } + require.NoErrorf(t, err, "ParseKubeVersion()") + assert.Equalf(t, tt.wantVer, kv.Version, "Version = %q, want %q", kv.Version, tt.wantVer) + assert.Equalf(t, tt.wantString, kv.String(), "String() = %q, want %q", kv.String(), tt.wantString) + assert.Equalf(t, tt.wantMajor, kv.Major, "Major = %q, want %q", kv.Major, tt.wantMajor) + assert.Equalf(t, tt.wantMinor, kv.Minor, "Minor = %q, want %q", kv.Minor, tt.wantMinor) }) } } diff --git a/pkg/chart/v2/lint/rules/template_test.go b/pkg/chart/v2/lint/rules/template_test.go index 12eec7531..37a33a7eb 100644 --- a/pkg/chart/v2/lint/rules/template_test.go +++ b/pkg/chart/v2/lint/rules/template_test.go @@ -20,7 +20,6 @@ import ( "fmt" "os" "path/filepath" - "strings" "testing" "time" @@ -38,17 +37,11 @@ const templateTestBasedir = "./testdata/albatross" func TestValidateAllowedExtension(t *testing.T) { var failTest = []string{"/foo", "/test.toml"} for _, test := range failTest { - err := validateAllowedExtension(test) - if err == nil || !strings.Contains(err.Error(), "Valid extensions are .yaml, .yml, .tpl, or .txt") { - t.Errorf("validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test) - } + require.ErrorContainsf(t, validateAllowedExtension(test), "Valid extensions are .yaml, .yml, .tpl, or .txt", "validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test) } var successTest = []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"} for _, test := range successTest { - err := validateAllowedExtension(test) - if err != nil { - t.Errorf("validateAllowedExtension('%s') to return no error but got \"%s\"", test, err.Error()) - } + assert.NoError(t, validateAllowedExtension(test), "validateAllowedExtension('%s') to return no error", test) } } @@ -65,13 +58,8 @@ func TestTemplateParsing(t *testing.T) { TemplateLinterSkipSchemaValidation(false)) res := linter.Messages - if len(res) != 1 { - t.Fatalf("Expected one error, got %d, %v", len(res), res) - } - - if !strings.Contains(res[0].Err.Error(), "deliberateSyntaxError") { - t.Errorf("Unexpected error: %s", res[0]) - } + require.Len(t, res, 1, "Expected one error, got %d, %v", len(res), res) + assert.ErrorContains(t, res[0].Err, "deliberateSyntaxError") } var wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml") @@ -92,9 +80,7 @@ func TestTemplateIntegrationHappyPath(t *testing.T) { TemplateLinterSkipSchemaValidation(false)) res := linter.Messages - if len(res) != 0 { - t.Fatalf("Expected no error, got %d, %v", len(res), res) - } + require.Empty(t, res, "Expected no error, got %d, %v", len(res), res) } func TestMultiTemplateFail(t *testing.T) { @@ -106,13 +92,8 @@ func TestMultiTemplateFail(t *testing.T) { TemplateLinterSkipSchemaValidation(false)) res := linter.Messages - if len(res) != 1 { - t.Fatalf("Expected 1 error, got %d, %v", len(res), res) - } - - if !strings.Contains(res[0].Err.Error(), "object name does not conform to Kubernetes naming requirements") { - t.Errorf("Unexpected error: %s", res[0].Err) - } + require.Len(t, res, 1, "Expected 1 error, got %d, %v", len(res), res) + assert.ErrorContains(t, res[0].Err, "object name does not conform to Kubernetes naming requirements") } func TestValidateMetadataName(t *testing.T) { @@ -221,9 +202,7 @@ func TestDeprecatedAPIFails(t *testing.T) { } tmpdir := t.TempDir() - if err := chartutil.SaveDir(&mychart, tmpdir); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(&mychart, tmpdir)) linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} Templates( @@ -279,9 +258,7 @@ func TestStrictTemplateParsingMapError(t *testing.T) { }, } dir := t.TempDir() - if err := chartutil.SaveDir(&ch, dir); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(&ch, dir)) linter := &support.Linter{ ChartDir: filepath.Join(dir, ch.Metadata.Name), } @@ -327,9 +304,7 @@ spec: - name: nginx image: nginx:1.14.2 ` - if err := validateMatchSelector(md, manifest); err != nil { - t.Error(err) - } + require.NoError(t, validateMatchSelector(md, manifest)) manifest = ` apiVersion: apps/v1 kind: Deployment @@ -351,9 +326,7 @@ spec: - name: nginx image: nginx:1.14.2 ` - if err := validateMatchSelector(md, manifest); err != nil { - t.Error(err) - } + require.NoError(t, validateMatchSelector(md, manifest)) manifest = ` apiVersion: apps/v1 kind: Deployment @@ -372,9 +345,7 @@ spec: - name: nginx image: nginx:1.14.2 ` - if err := validateMatchSelector(md, manifest); err == nil { - t.Error("expected Deployment with no selector to fail") - } + assert.Error(t, validateMatchSelector(md, manifest), "expected Deployment with no selector to fail") } func TestValidateTopIndentLevel(t *testing.T) { @@ -413,9 +384,7 @@ func TestEmptyWithCommentsManifests(t *testing.T) { } tmpdir := t.TempDir() - if err := chartutil.SaveDir(&mychart, tmpdir); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(&mychart, tmpdir)) linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} Templates( @@ -448,10 +417,7 @@ items: annotations: helm.sh/resource-policy: keep ` - - if err := validateListAnnotations(md, manifest); err == nil { - t.Fatal("expected list with nested keep annotations to fail") - } + require.Error(t, validateListAnnotations(md, manifest), "expected list with nested keep annotations to fail") manifest = ` apiVersion: v1 @@ -463,10 +429,7 @@ items: - apiVersion: v1 kind: ConfigMap ` - - if err := validateListAnnotations(md, manifest); err != nil { - t.Fatalf("List objects keep annotations should pass. got: %s", err) - } + require.NoErrorf(t, validateListAnnotations(md, manifest), "List objects keep annotations should pass. got") } func TestIsYamlFileExtension(t *testing.T) { @@ -482,8 +445,6 @@ func TestIsYamlFileExtension(t *testing.T) { for _, test := range tests { result := isYamlFileExtension(test.filename) - if result != test.expected { - t.Errorf("isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected) - } + assert.Equal(t, test.expected, result, "isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected) } } diff --git a/pkg/cmd/dependency_build_test.go b/pkg/cmd/dependency_build_test.go index 7b3bea28a..485ae18b3 100644 --- a/pkg/cmd/dependency_build_test.go +++ b/pkg/cmd/dependency_build_test.go @@ -19,9 +19,11 @@ import ( "fmt" "os" "path/filepath" - "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" "helm.sh/helm/v4/pkg/provenance" "helm.sh/helm/v4/pkg/repo/v1" @@ -39,15 +41,12 @@ func TestDependencyBuildCmd(t *testing.T) { srv.LinkIndices() ociSrv, err := repotest.NewOCIServer(t, srv.Root()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) ociChartName := "oci-depending-chart" c := createTestingMetadataForOCI(ociChartName, ociSrv.RegistryURL) - if _, err := chartutil.Save(c, ociSrv.Dir); err != nil { - t.Fatal(err) - } + _, err = chartutil.Save(c, ociSrv.Dir) + require.NoError(t, err) ociSrv.Run(t, repotest.WithDependingChart(c)) dir := func(p ...string) string { @@ -67,25 +66,19 @@ func TestDependencyBuildCmd(t *testing.T) { t.Fatal(err) } - if !strings.Contains(out, `update from the "test" chart repository`) { - t.Errorf("Repo did not get updated\n%s", out) - } + assert.Contains(t, out, `update from the "test" chart repository`, "Repo did not get updated\n%s", out) // Make sure the actual file got downloaded. expect := filepath.Join(rootDir, chartname, "charts", "reqtest-0.1.0.tgz") - if _, err := os.Stat(expect); err != nil { - t.Fatal(err) - } + _, err = os.Stat(expect) + require.NoError(t, err) // In the second pass, we want to remove the chart's request dependency, // then see if it restores from the lock. lockfile := filepath.Join(rootDir, chartname, "Chart.lock") - if _, err := os.Stat(lockfile); err != nil { - t.Fatal(err) - } - if err := os.RemoveAll(expect); err != nil { - t.Fatal(err) - } + _, err = os.Stat(lockfile) + require.NoError(t, err) + require.NoError(t, os.RemoveAll(expect)) _, out, err = executeActionCommand(cmd) if err != nil { @@ -94,28 +87,21 @@ func TestDependencyBuildCmd(t *testing.T) { } // Now repeat the test that the dependency exists. - if _, err := os.Stat(expect); err != nil { - t.Fatal(err) - } + _, err = os.Stat(expect) + require.NoError(t, err) // Make sure that build is also fetching the correct version. hash, err := provenance.DigestFile(expect) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) i, err := repo.LoadIndexFile(filepath.Join(rootDir, "index.yaml")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) reqver := i.Entries["reqtest"][0] - if h := reqver.Digest; h != hash { - t.Errorf("Failed hash match: expected %s, got %s", hash, h) - } - if v := reqver.Version; v != "0.1.0" { - t.Errorf("mismatched versions. Expected %q, got %q", "0.1.0", v) - } + h := reqver.Digest + assert.Equalf(t, h, hash, "Failed hash match: expected %s, got %s", hash, h) + v := reqver.Version + assert.Equalf(t, "0.1.0", v, "mismatched versions. Expected %q, got %q", "0.1.0", v) skipRefreshCmd := fmt.Sprintf("dependency build '%s' --skip-refresh --repository-config %s --repository-cache %s --plain-http", filepath.Join(rootDir, chartname), repoFile, rootDir) _, out, err = executeActionCommand(skipRefreshCmd) @@ -126,14 +112,10 @@ func TestDependencyBuildCmd(t *testing.T) { t.Fatal(err) } - if strings.Contains(out, `update from the "test" chart repository`) { - t.Errorf("Repo did get updated\n%s", out) - } + assert.NotContains(t, out, `update from the "test" chart repository`, "Repo did get updated\n%s", out) // OCI dependencies - if err := chartutil.SaveDir(c, dir()); err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(c, dir())) cmd = fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json --plain-http", dir(ociChartName), dir("repositories.yaml"), @@ -145,9 +127,8 @@ func TestDependencyBuildCmd(t *testing.T) { t.Fatal(err) } expect = dir(ociChartName, "charts/oci-dependent-chart-0.1.0.tgz") - if _, err := os.Stat(expect); err != nil { - t.Fatal(err) - } + _, err = os.Stat(expect) + require.NoError(t, err) } func TestDependencyBuildCmdWithHelmV2Hash(t *testing.T) { diff --git a/pkg/cmd/plugin_package_test.go b/pkg/cmd/plugin_package_test.go index b5ecf2bf1..e04498b98 100644 --- a/pkg/cmd/plugin_package_test.go +++ b/pkg/cmd/plugin_package_test.go @@ -19,8 +19,10 @@ import ( "bytes" "os" "path/filepath" - "strings" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // Common plugin.yaml content for v1 format tests @@ -43,14 +45,10 @@ func TestPluginPackageWithoutSigning(t *testing.T) { // Create a test plugin directory tempDir := t.TempDir() pluginDir := filepath.Join(tempDir, "test-plugin") - if err := os.MkdirAll(pluginDir, 0o755); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(pluginDir, 0o755)) // Create a plugin.yaml file - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644); err != nil { - t.Fatal(err) - } + require.NoError(t, os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644)) // Create package options with sign=false o := &pluginPackageOptions{ @@ -61,47 +59,34 @@ func TestPluginPackageWithoutSigning(t *testing.T) { // Run the package command out := &bytes.Buffer{} - err := o.run(out) // Should succeed without error - if err != nil { - t.Errorf("unexpected error: %v", err) - } + require.NoError(t, o.run(out)) // Check that tarball was created with plugin name and version tarballPath := filepath.Join(tempDir, "test-plugin-1.0.0.tgz") - if _, err := os.Stat(tarballPath); os.IsNotExist(err) { - t.Error("tarball should exist when sign=false") - } + _, err := os.Stat(tarballPath) + assert.False(t, os.IsNotExist(err), "tarball should exist when sign=false") // Check that no .prov file was created provPath := tarballPath + ".prov" - if _, err := os.Stat(provPath); !os.IsNotExist(err) { - t.Error("provenance file should not exist when sign=false") - } + _, err = os.Stat(provPath) + assert.True(t, os.IsNotExist(err), "provenance file should not exist when sign=false") // Output should contain warning about skipping signing output := out.String() - if !strings.Contains(output, "WARNING: Skipping plugin signing") { - t.Error("should print warning when signing is skipped") - } - if !strings.Contains(output, "Successfully packaged") { - t.Error("should print success message") - } + assert.Contains(t, output, "WARNING: Skipping plugin signing", "should print warning when signing is skipped") + assert.Contains(t, output, "Successfully packaged", "should print success message") } func TestPluginPackageDefaultRequiresSigning(t *testing.T) { // Create a test plugin directory tempDir := t.TempDir() pluginDir := filepath.Join(tempDir, "test-plugin") - if err := os.MkdirAll(pluginDir, 0o755); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(pluginDir, 0o755)) // Create a plugin.yaml file - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644); err != nil { - t.Fatal(err) - } + require.NoError(t, os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644)) // Create package options with default sign=true and invalid keyring o := &pluginPackageOptions{ @@ -113,32 +98,24 @@ func TestPluginPackageDefaultRequiresSigning(t *testing.T) { // Run the package command out := &bytes.Buffer{} - err := o.run(out) // Should fail because signing is required by default - if err == nil { - t.Error("expected error when signing fails with default settings") - } + require.Error(t, o.run(out), "expected error when signing fails with default settings") // Check that no tarball was created tarballPath := filepath.Join(tempDir, "test-plugin.tgz") - if _, err := os.Stat(tarballPath); !os.IsNotExist(err) { - t.Error("tarball should not exist when signing fails") - } + _, err := os.Stat(tarballPath) + assert.True(t, os.IsNotExist(err), "tarball should not exist when signing fails") } func TestPluginPackageSigningFailure(t *testing.T) { // Create a test plugin directory tempDir := t.TempDir() pluginDir := filepath.Join(tempDir, "test-plugin") - if err := os.MkdirAll(pluginDir, 0o755); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(pluginDir, 0o755)) // Create a plugin.yaml file - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644); err != nil { - t.Fatal(err) - } + require.NoError(t, os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644)) // Create package options with sign flag but invalid keyring o := &pluginPackageOptions{ @@ -150,21 +127,15 @@ func TestPluginPackageSigningFailure(t *testing.T) { // Run the package command out := &bytes.Buffer{} - err := o.run(out) // Should get an error - if err == nil { - t.Error("expected error when signing fails, got nil") - } + require.Error(t, o.run(out), "expected error when signing fails, got nil") // Check that no tarball was created tarballPath := filepath.Join(tempDir, "test-plugin.tgz") - if _, err := os.Stat(tarballPath); !os.IsNotExist(err) { - t.Error("tarball should not exist when signing fails") - } + _, err := os.Stat(tarballPath) + assert.True(t, os.IsNotExist(err), "tarball should not exist when signing fails") // Output should not contain success message - if bytes.Contains(out.Bytes(), []byte("Successfully packaged")) { - t.Error("should not print success message when signing fails") - } + assert.False(t, bytes.Contains(out.Bytes(), []byte("Successfully packaged")), "should not print success message when signing fails") } diff --git a/pkg/cmd/repo_add_test.go b/pkg/cmd/repo_add_test.go index ed677f98a..6ed2a7422 100644 --- a/pkg/cmd/repo_add_test.go +++ b/pkg/cmd/repo_add_test.go @@ -17,16 +17,16 @@ limitations under the License. package cmd import ( - "errors" "fmt" "io" "io/fs" "os" "path/filepath" - "strings" "sync" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "sigs.k8s.io/yaml" "helm.sh/helm/v4/pkg/helmpath" @@ -50,9 +50,7 @@ func TestRepoAddCmd(t *testing.T) { defer srv2.Stop() tmpdir := filepath.Join(t.TempDir(), "path-component.yaml", "data") - if err := os.MkdirAll(tmpdir, 0o777); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(tmpdir, 0o777)) repoFile := filepath.Join(tmpdir, "repositories.yaml") tests := []cmdTestCase{ @@ -101,37 +99,24 @@ func TestRepoAdd(t *testing.T) { } t.Setenv(xdg.CacheHomeEnvVar, rootDir) - if err := o.run(io.Discard); err != nil { - t.Error(err) - } + require.NoError(t, o.run(io.Discard)) f, err := repo.LoadFile(repoFile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if !f.Has(testRepoName) { - t.Errorf("%s was not successfully inserted into %s", testRepoName, repoFile) - } + assert.Truef(t, f.Has(testRepoName), "%s was not successfully inserted into %s", testRepoName, repoFile) idx := filepath.Join(helmpath.CachePath("repository"), helmpath.CacheIndexFile(testRepoName)) - if _, err := os.Stat(idx); errors.Is(err, fs.ErrNotExist) { - t.Errorf("Error cache index file was not created for repository %s", testRepoName) - } + _, err = os.Stat(idx) + require.NotErrorIsf(t, err, fs.ErrNotExist, "Error cache index file was not created for repository %s", testRepoName) idx = filepath.Join(helmpath.CachePath("repository"), helmpath.CacheChartsFile(testRepoName)) - if _, err := os.Stat(idx); errors.Is(err, fs.ErrNotExist) { - t.Errorf("Error cache charts file was not created for repository %s", testRepoName) - } + _, err = os.Stat(idx) + require.NotErrorIsf(t, err, fs.ErrNotExist, "Error cache charts file was not created for repository %s", testRepoName) o.forceUpdate = true - if err := o.run(io.Discard); err != nil { - t.Errorf("Repository was not updated: %s", err) - } - - if err := o.run(io.Discard); err != nil { - t.Error("Duplicate repository name was added") - } + require.NoError(t, o.run(io.Discard), "Repository was not updated") + assert.NoError(t, o.run(io.Discard), "Duplicate repository name was added") } func TestRepoAddCheckLegalName(t *testing.T) { @@ -156,14 +141,7 @@ func TestRepoAddCheckLegalName(t *testing.T) { t.Setenv(xdg.CacheHomeEnvVar, rootDir) wantErrorMsg := fmt.Sprintf("repository name (%s) contains '/', please specify a different name without '/'", testRepoName) - - if err := o.run(io.Discard); err != nil { - if wantErrorMsg != err.Error() { - t.Fatalf("Actual error %s, not equal to expected error %s", err, wantErrorMsg) - } - } else { - t.Fatal("expect reported an error.") - } + require.EqualError(t, o.run(io.Discard), wantErrorMsg) } func TestRepoAddConcurrentGoRoutines(t *testing.T) { @@ -209,29 +187,21 @@ func repoAddConcurrent(t *testing.T, testName, repoFile string) { forceUpdate: false, repoFile: repoFile, } - if err := o.run(io.Discard); err != nil { - t.Error(err) - } + assert.NoError(t, o.run(io.Discard)) }(fmt.Sprintf("%s-%d", testName, i)) } wg.Wait() b, err := os.ReadFile(repoFile) - if err != nil { - t.Error(err) - } + require.NoError(t, err) var f repo.File - if err := yaml.Unmarshal(b, &f); err != nil { - t.Error(err) - } + require.NoError(t, yaml.Unmarshal(b, &f)) var name string for i := range 3 { name = fmt.Sprintf("%s-%d", testName, i) - if !f.Has(name) { - t.Errorf("%s was not successfully inserted into %s: %s", name, repoFile, f.Repositories[0]) - } + assert.Truef(t, f.Has(name), "%s was not successfully inserted into %s: %s", name, repoFile, f.Repositories[0]) } } @@ -252,9 +222,7 @@ func TestRepoAddWithPasswordFromStdin(t *testing.T) { defer resetEnv()() in, err := os.Open("testdata/password") - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) tmpdir := t.TempDir() repoFile := filepath.Join(tmpdir, "repositories.yaml") @@ -266,11 +234,7 @@ func TestRepoAddWithPasswordFromStdin(t *testing.T) { cmd := fmt.Sprintf("repo add %s %s --repository-config %s --repository-cache %s --username %s --password-stdin", testName, srv.URL(), repoFile, tmpdir, username) var result string _, result, err = executeActionCommandStdinC(store, in, cmd) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } - if !strings.Contains(result, fmt.Sprintf("%q has been added to your repositories", testName)) { - t.Errorf("Repo was not successfully added. Output: %s", result) - } + require.NoError(t, err) + assert.Contains(t, result, fmt.Sprintf("%q has been added to your repositories", testName), "Repo was not successfully added. Output: %s", result) } From 0fa9f7d427b99437e51dbb8ada21d19f93898652 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:56 +0200 Subject: [PATCH 473/571] chore(pkg): refactor: convert tests to testify assert/require part 16 #### Description refactor: convert tests to testify assert/require in pkg/cmd Signed-off-by: Matthieu MOREL --- pkg/action/get_metadata_test.go | 6 +- pkg/chart/common/values_test.go | 116 ++++++++++++------------------- pkg/chart/v2/util/expand_test.go | 92 +++++++----------------- pkg/cmd/plugin_verify_test.go | 83 +++++----------------- pkg/cmd/pull_test.go | 105 +++++++++------------------- 5 files changed, 120 insertions(+), 282 deletions(-) diff --git a/pkg/action/get_metadata_test.go b/pkg/action/get_metadata_test.go index 37f20fb60..f8851e456 100644 --- a/pkg/action/get_metadata_test.go +++ b/pkg/action/get_metadata_test.go @@ -65,8 +65,7 @@ func TestGetMetadata_Run_BasicMetadata(t *testing.T) { Namespace: "default", } - err := cfg.Releases.Create(rel) - require.NoError(t, err) + require.NoError(t, cfg.Releases.Create(rel)) result, err := client.Run(releaseName) require.NoError(t, err) @@ -645,8 +644,7 @@ func TestGetMetadata_Labels(t *testing.T) { rel.Labels = customLabels metaGetter := NewGetMetadata(actionConfigFixture(t)) - err := metaGetter.cfg.Releases.Create(rel) - require.NoError(t, err) + require.NoError(t, metaGetter.cfg.Releases.Create(rel)) metadata, err := metaGetter.Run(rel.Name) require.NoError(t, err) diff --git a/pkg/chart/common/values_test.go b/pkg/chart/common/values_test.go index 9743869ec..803513c32 100644 --- a/pkg/chart/common/values_test.go +++ b/pkg/chart/common/values_test.go @@ -21,6 +21,9 @@ import ( "fmt" "testing" "text/template" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestReadValues(t *testing.T) { @@ -46,29 +49,21 @@ water: ` data, err := ReadValues([]byte(doc)) - if err != nil { - t.Fatalf("Error parsing bytes: %s", err) - } + require.NoErrorf(t, err, "Error parsing bytes") matchValues(t, data) tests := []string{`poet: "Coleridge"`, "# Just a comment", ""} for _, tt := range tests { data, err = ReadValues([]byte(tt)) - if err != nil { - t.Fatalf("Error parsing bytes (%s): %s", tt, err) - } - if data == nil { - t.Errorf(`YAML string "%s" gave a nil map`, tt) - } + require.NoErrorf(t, err, "Error parsing bytes (%s)", tt) + require.NotNilf(t, data, `YAML string "%s" gave a nil map`, tt) } } func TestReadValuesFile(t *testing.T) { data, err := ReadValuesFile("./testdata/coleridge.yaml") - if err != nil { - t.Fatalf("Error reading YAML file: %s", err) - } + require.NoErrorf(t, err, "Error reading YAML file") matchValues(t, data) } @@ -108,56 +103,40 @@ chapter: title: "The Spouter Inn" ` d, err := ReadValues([]byte(doc)) - if err != nil { - t.Fatalf("Failed to parse the White Whale: %s", err) - } + require.NoErrorf(t, err, "Failed to parse the White Whale") - if _, err := d.Table("title"); err == nil { - t.Fatal("Title is not a table.") - } + _, err = d.Table("title") + require.Error(t, err, "Title is not a table.") - if _, err := d.Table("chapter"); err != nil { - t.Fatalf("Failed to get the chapter table: %s\n%v", err, d) - } + _, err = d.Table("chapter") + require.NoErrorf(t, err, "Failed to get the chapter table: %v", d) - if v, err := d.Table("chapter.one"); err != nil { - t.Errorf("Failed to get chapter.one: %s", err) - } else if v["title"] != "Loomings" { - t.Errorf("Unexpected title: %s", v["title"]) - } + v, err := d.Table("chapter.one") + require.NoErrorf(t, err, "Failed to get chapter.one") + assert.Equalf(t, "Loomings", v["title"], "Unexpected title: %s", v["title"]) - if _, err := d.Table("chapter.three"); err != nil { - t.Errorf("Chapter three is missing: %s\n%v", err, d) - } + _, err = d.Table("chapter.three") + require.NoErrorf(t, err, "Chapter three is missing: %v", d) - if _, err := d.Table("chapter.OneHundredThirtySix"); err == nil { - t.Error("I think you mean 'Epilogue'") - } + _, err = d.Table("chapter.OneHundredThirtySix") + assert.Error(t, err, "I think you mean 'Epilogue'") } func matchValues(t *testing.T, data map[string]any) { t.Helper() - if data["poet"] != "Coleridge" { - t.Errorf("Unexpected poet: %s", data["poet"]) - } + assert.Equalf(t, "Coleridge", data["poet"], "Unexpected poet: %s", data["poet"]) - if o, err := ttpl("{{len .stanza}}", data); err != nil { - t.Errorf("len stanza: %s", err) - } else if o != "6" { - t.Errorf("Expected 6, got %s", o) - } + o, err := ttpl("{{len .stanza}}", data) + require.NoErrorf(t, err, "len stanza") + assert.Equalf(t, "6", o, "Expected 6, got %s", o) - if o, err := ttpl("{{.mariner.shot}}", data); err != nil { - t.Errorf(".mariner.shot: %s", err) - } else if o != "ALBATROSS" { - t.Error("Expected that mariner shot ALBATROSS") - } + o, err = ttpl("{{.mariner.shot}}", data) + require.NoErrorf(t, err, ".mariner.shot") + assert.Equal(t, "ALBATROSS", o, "Expected that mariner shot ALBATROSS") - if o, err := ttpl("{{.water.water.where}}", data); err != nil { - t.Errorf(".water.water.where: %s", err) - } else if o != "everywhere" { - t.Error("Expected water water everywhere") - } + o, err = ttpl("{{.water.water.where}}", data) + require.NoErrorf(t, err, ".water.water.where") + assert.Equal(t, "everywhere", o, "Expected water water everywhere") } func ttpl(tpl string, v map[string]any) (string, error) { @@ -179,27 +158,18 @@ chapter: title: "The Spouter Inn" ` d, err := ReadValues([]byte(doc)) - if err != nil { - t.Fatalf("Failed to parse the White Whale: %s", err) - } - - if v, err := d.PathValue("chapter.one.title"); err != nil { - t.Errorf("Got error instead of title: %s\n%v", err, d) - } else if v != "Loomings" { - t.Errorf("No error but got wrong value for title: %s\n%v", err, d) - } - if _, err := d.PathValue("chapter.one.doesnotexist"); err == nil { - t.Errorf("Non-existent key should return error: %s\n%v", err, d) - } - if _, err := d.PathValue("chapter.doesnotexist.one"); err == nil { - t.Errorf("Non-existent key in middle of path should return error: %s\n%v", err, d) - } - if _, err := d.PathValue(""); err == nil { - t.Error("Asking for the value from an empty path should yield an error") - } - if v, err := d.PathValue("title"); err == nil { - if v != "Moby Dick" { - t.Error("Failed to return values for root key title") - } - } + require.NoErrorf(t, err, "Failed to parse the White Whale") + + v, err := d.PathValue("chapter.one.title") + require.NoErrorf(t, err, "Got error instead of title: %v", d) + assert.Equalf(t, "Loomings", v, "No error but got wrong value for title: %v", d) + _, err = d.PathValue("chapter.one.doesnotexist") + require.Errorf(t, err, "Non-existent key should return error: %v", d) + _, err = d.PathValue("chapter.doesnotexist.one") + require.Errorf(t, err, "Non-existent key in middle of path should return error: %v", d) + _, err = d.PathValue("") + require.Error(t, err, "Asking for the value from an empty path should yield an error") + v, err = d.PathValue("title") + require.NoErrorf(t, err, "Failed to get title: %v", d) + assert.Equalf(t, "Moby Dick", v, "Failed to return values for root key title: got %s\n%v", v, d) } diff --git a/pkg/chart/v2/util/expand_test.go b/pkg/chart/v2/util/expand_test.go index e9e298b81..bc8cc7855 100644 --- a/pkg/chart/v2/util/expand_test.go +++ b/pkg/chart/v2/util/expand_test.go @@ -47,19 +47,18 @@ func makeTestChartArchive(t *testing.T, chartName, sourceDir string) *bytes.Buff fStat, err := f.Stat() require.NoError(t, err) - err = tw.WriteHeader(&tar.Header{ + require.NoError(t, tw.WriteHeader(&tar.Header{ Name: filepath.Join(chartName, relPath), Mode: int64(fStat.Mode()), Size: fStat.Size(), - }) - require.NoError(t, err) + })) data, err := fs.ReadFile(dir, relPath) require.NoError(t, err) tw.Write(data) } - err := fs.WalkDir(dir, ".", func(path string, d os.DirEntry, walkErr error) error { + require.NoError(t, fs.WalkDir(dir, ".", func(path string, d os.DirEntry, walkErr error) error { if walkErr != nil { return walkErr } @@ -71,15 +70,9 @@ func makeTestChartArchive(t *testing.T, chartName, sourceDir string) *bytes.Buff writeFile(path) return nil - }) - if err != nil { - t.Fatal(err) - } - - err = tw.Close() - require.NoError(t, err) - err = gw.Close() - require.NoError(t, err) + })) + require.NoError(t, tw.Close()) + require.NoError(t, gw.Close()) return &result } @@ -88,51 +81,33 @@ func TestExpand(t *testing.T) { dest := t.TempDir() reader, err := os.Open("testdata/frobnitz-1.2.3.tgz") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if err := Expand(dest, reader); err != nil { - t.Fatal(err) - } + require.NoError(t, Expand(dest, reader)) expectedChartPath := filepath.Join(dest, "frobnitz") fi, err := os.Stat(expectedChartPath) - if err != nil { - t.Fatal(err) - } - if !fi.IsDir() { - t.Fatalf("expected a chart directory at %s", expectedChartPath) - } + require.NoError(t, err) + require.Truef(t, fi.IsDir(), "expected a chart directory at %s", expectedChartPath) dir, err := os.Open(expectedChartPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) fis, err := dir.Readdir(0) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) expectLen := 11 - if len(fis) != expectLen { - t.Errorf("Expected %d files, but got %d", expectLen, len(fis)) - } + assert.Len(t, fis, expectLen, "Expected %d files, but got %d", expectLen, len(fis)) for _, fi := range fis { expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name())) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // os.Stat can return different values for directories, based on the OS // for Linux, for example, os.Stat always returns the size of the directory // (value-4096) regardless of the size of the contents of the directory mode := expect.Mode() if !mode.IsDir() { - if fi.Size() != expect.Size() { - t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) - } + assert.Equal(t, expect.Size(), fi.Size(), "Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) } } } @@ -151,10 +126,9 @@ func TestExpandError(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { archive := makeTestChartArchive(t, tt.chartName, tt.chartDir) - dest := t.TempDir() - err := Expand(dest, archive) - assert.ErrorContains(t, err, tt.wantErr) + + assert.ErrorContains(t, Expand(dest, archive), tt.wantErr) }) } } @@ -162,47 +136,31 @@ func TestExpandError(t *testing.T) { func TestExpandFile(t *testing.T) { dest := t.TempDir() - if err := ExpandFile(dest, "testdata/frobnitz-1.2.3.tgz"); err != nil { - t.Fatal(err) - } + require.NoError(t, ExpandFile(dest, "testdata/frobnitz-1.2.3.tgz")) expectedChartPath := filepath.Join(dest, "frobnitz") fi, err := os.Stat(expectedChartPath) - if err != nil { - t.Fatal(err) - } - if !fi.IsDir() { - t.Fatalf("expected a chart directory at %s", expectedChartPath) - } + require.NoError(t, err) + require.Truef(t, fi.IsDir(), "expected a chart directory at %s", expectedChartPath) dir, err := os.Open(expectedChartPath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) fis, err := dir.Readdir(0) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) expectLen := 11 - if len(fis) != expectLen { - t.Errorf("Expected %d files, but got %d", expectLen, len(fis)) - } + assert.Len(t, fis, expectLen, "Expected %d files, but got %d", expectLen, len(fis)) for _, fi := range fis { expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name())) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // os.Stat can return different values for directories, based on the OS // for Linux, for example, os.Stat always returns the size of the directory // (value-4096) regardless of the size of the contents of the directory mode := expect.Mode() if !mode.IsDir() { - if fi.Size() != expect.Size() { - t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) - } + assert.Equal(t, expect.Size(), fi.Size(), "Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) } } } diff --git a/pkg/cmd/plugin_verify_test.go b/pkg/cmd/plugin_verify_test.go index 4ce664c0c..64e6cc18c 100644 --- a/pkg/cmd/plugin_verify_test.go +++ b/pkg/cmd/plugin_verify_test.go @@ -21,9 +21,11 @@ import ( "fmt" "os" "path/filepath" - "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/internal/plugin" "helm.sh/helm/v4/internal/test/ensure" ) @@ -35,13 +37,7 @@ func TestPluginVerifyCmd_NoArgs(t *testing.T) { cmd := newPluginVerifyCmd(out) cmd.SetArgs([]string{}) - err := cmd.Execute() - if err == nil { - t.Error("expected error when no arguments provided") - } - if !strings.Contains(err.Error(), "requires 1 argument") { - t.Errorf("expected 'requires 1 argument' error, got: %v", err) - } + assert.ErrorContains(t, cmd.Execute(), "requires 1 argument", "expected 'requires 1 argument' error") } func TestPluginVerifyCmd_TooManyArgs(t *testing.T) { @@ -51,13 +47,7 @@ func TestPluginVerifyCmd_TooManyArgs(t *testing.T) { cmd := newPluginVerifyCmd(out) cmd.SetArgs([]string{"plugin1", "plugin2"}) - err := cmd.Execute() - if err == nil { - t.Error("expected error when too many arguments provided") - } - if !strings.Contains(err.Error(), "requires 1 argument") { - t.Errorf("expected 'requires 1 argument' error, got: %v", err) - } + assert.ErrorContains(t, cmd.Execute(), "requires 1 argument", "expected 'requires 1 argument' error") } func TestPluginVerifyCmd_NonexistentFile(t *testing.T) { @@ -67,10 +57,7 @@ func TestPluginVerifyCmd_NonexistentFile(t *testing.T) { cmd := newPluginVerifyCmd(out) cmd.SetArgs([]string{"/nonexistent/plugin.tgz"}) - err := cmd.Execute() - if err == nil { - t.Error("expected error when plugin file doesn't exist") - } + assert.Error(t, cmd.Execute(), "expected error when plugin file doesn't exist") } func TestPluginVerifyCmd_MissingProvenance(t *testing.T) { @@ -84,13 +71,7 @@ func TestPluginVerifyCmd_MissingProvenance(t *testing.T) { cmd := newPluginVerifyCmd(out) cmd.SetArgs([]string{pluginTgz}) - err := cmd.Execute() - if err == nil { - t.Error("expected error when .prov file is missing") - } - if !strings.Contains(err.Error(), "could not find provenance file") { - t.Errorf("expected 'could not find provenance file' error, got: %v", err) - } + assert.ErrorContains(t, cmd.Execute(), "could not find provenance file", "expected 'could not find provenance file' error") } func TestPluginVerifyCmd_InvalidProvenance(t *testing.T) { @@ -102,19 +83,14 @@ func TestPluginVerifyCmd_InvalidProvenance(t *testing.T) { // Create invalid .prov file provFile := pluginTgz + ".prov" - if err := os.WriteFile(provFile, []byte("invalid provenance"), 0o644); err != nil { - t.Fatal(err) - } + require.NoError(t, os.WriteFile(provFile, []byte("invalid provenance"), 0o644)) defer os.Remove(provFile) out := &bytes.Buffer{} cmd := newPluginVerifyCmd(out) cmd.SetArgs([]string{pluginTgz}) - err := cmd.Execute() - if err == nil { - t.Error("expected error when .prov file is invalid") - } + assert.Error(t, cmd.Execute(), "expected error when .prov file is invalid") } func TestPluginVerifyCmd_DirectoryNotSupported(t *testing.T) { @@ -127,13 +103,7 @@ func TestPluginVerifyCmd_DirectoryNotSupported(t *testing.T) { cmd := newPluginVerifyCmd(out) cmd.SetArgs([]string{pluginDir}) - err := cmd.Execute() - if err == nil { - t.Error("expected error when verifying directory") - } - if !strings.Contains(err.Error(), "directory verification not supported") { - t.Errorf("expected 'directory verification not supported' error, got: %v", err) - } + assert.ErrorContains(t, cmd.Execute(), "directory verification not supported", "expected 'directory verification not supported' error") } func TestPluginVerifyCmd_KeyringFlag(t *testing.T) { @@ -157,10 +127,7 @@ func TestPluginVerifyCmd_KeyringFlag(t *testing.T) { cmd.SetArgs([]string{"--keyring", keyring, pluginTgz}) // Should fail with keyring error but command parsing should work - err := cmd.Execute() - if err == nil { - t.Error("expected error with empty keyring") - } + assert.Error(t, cmd.Execute(), "expected error with empty keyring") // The important thing is that the keyring flag was parsed and used } @@ -178,14 +145,10 @@ func createTestPluginDir(t *testing.T) string { // Create temporary directory with plugin structure tmpDir := t.TempDir() pluginDir := filepath.Join(tmpDir, "test-plugin") - if err := os.MkdirAll(pluginDir, 0o755); err != nil { - t.Fatalf("Failed to create plugin directory: %v", err) - } + require.NoErrorf(t, os.MkdirAll(pluginDir, 0o755), "Failed to create plugin directory") // Use the same plugin YAML as other cmd tests - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644); err != nil { - t.Fatalf("Failed to create plugin.yaml: %v", err) - } + require.NoErrorf(t, os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(testPluginYAML), 0o644), "Failed to create plugin.yaml") return pluginDir } @@ -199,14 +162,10 @@ func createTestPluginTarball(t *testing.T) string { tmpDir := filepath.Dir(pluginDir) tgzPath := filepath.Join(tmpDir, "test-plugin-1.0.0.tgz") tarFile, err := os.Create(tgzPath) - if err != nil { - t.Fatalf("Failed to create tarball file: %v", err) - } + require.NoError(t, err, "Failed to create tarball file") defer tarFile.Close() - if err := plugin.CreatePluginTarball(pluginDir, "test-plugin", tarFile); err != nil { - t.Fatalf("Failed to create tarball: %v", err) - } + require.NoErrorf(t, plugin.CreatePluginTarball(pluginDir, "test-plugin", tarFile), "Failed to create tarball") return tgzPath } @@ -218,9 +177,7 @@ func createProvFile(t *testing.T, provFile, pluginTgz, hash string) { if hash == "" { // Calculate actual hash of the tarball data, err := os.ReadFile(pluginTgz) - if err != nil { - t.Fatalf("Failed to read tarball for hashing: %v", err) - } + require.NoError(t, err, "Failed to read tarball for hashing") hashSum := sha256.Sum256(data) hashStr = fmt.Sprintf("sha256:%x", hashSum) } else { @@ -243,9 +200,7 @@ Version: GnuPG v1 iQEcBAEBCAAGBQJktest... -----END PGP SIGNATURE----- `, hashStr) - if err := os.WriteFile(provFile, []byte(provContent), 0o644); err != nil { - t.Fatalf("Failed to create provenance file: %v", err) - } + require.NoErrorf(t, os.WriteFile(provFile, []byte(provContent), 0o644), "Failed to create provenance file") } func createTestKeyring(t *testing.T) string { @@ -256,9 +211,7 @@ func createTestKeyring(t *testing.T) string { keyringPath := filepath.Join(tmpDir, "pubring.gpg") // Create empty keyring for testing - if err := os.WriteFile(keyringPath, []byte{}, 0o644); err != nil { - t.Fatalf("Failed to create test keyring: %v", err) - } + require.NoErrorf(t, os.WriteFile(keyringPath, []byte{}, 0o644), "Failed to create test keyring") return keyringPath } diff --git a/pkg/cmd/pull_test.go b/pkg/cmd/pull_test.go index e1b3e8134..352c36a29 100644 --- a/pkg/cmd/pull_test.go +++ b/pkg/cmd/pull_test.go @@ -25,6 +25,9 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/repo/v1/repotest" ) @@ -36,14 +39,10 @@ func TestPullCmd(t *testing.T) { defer srv.Stop() ociSrv, err := repotest.NewOCIServer(t, srv.Root()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) ociSrv.Run(t) - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.LinkIndices()) helmTestKeyOut := "Signed by: Helm Testing (This key should only be used for testing. DO NOT TRUST.) \n" + "Using Key With Fingerprint: 5E615389B53CA37F0EE60BD3843BBF981FC18762\n" + @@ -229,20 +228,13 @@ func TestPullCmd(t *testing.T) { // Create file or Dir before helm pull --untar, see: https://github.com/helm/helm/issues/7182 if tt.existFile != "" { file := filepath.Join(outdir, tt.existFile) - if err := os.MkdirAll(filepath.Dir(file), 0o755); err != nil { - t.Fatal(err) - } - _, err := os.Create(file) - if err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(filepath.Dir(file), 0o755)) + _, err = os.Create(file) + require.NoError(t, err) } if tt.existDir != "" { file := filepath.Join(outdir, tt.existDir) - err := os.MkdirAll(file, 0o755) - if err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(file, 0o755)) } _, out, err := executeActionCommand(cmd) if err != nil { @@ -257,19 +249,13 @@ func TestPullCmd(t *testing.T) { if tt.expectVerify { outString := helmTestKeyOut + tt.expectSha + "\n" - if out != outString { - t.Errorf("%q: expected verification output %q, got %q", tt.name, outString, out) - } + assert.Equal(t, outString, out, "%q: expected verification output %q, got %q", tt.name, outString, out) } ef := filepath.Join(outdir, tt.expectFile) fi, err := os.Stat(ef) - if err != nil { - t.Errorf("%q: expected a file at %s. %s", tt.name, ef, err) - } - if fi.IsDir() != tt.expectDir { - t.Errorf("%q: expected directory=%t, but it's not.", tt.name, tt.expectDir) - } + require.NoError(t, err, "%q: expected a file at %s.", tt.name, ef) + assert.Equal(t, tt.expectDir, fi.IsDir(), "%q: expected directory=%t, but it's not.", tt.name, tt.expectDir) }) } } @@ -300,38 +286,24 @@ func runPullTests(t *testing.T, tests []struct { if tt.existFile != "" { file := filepath.Join(outdir, tt.existFile) _, err := os.Create(file) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } if tt.existDir != "" { file := filepath.Join(outdir, tt.existDir) - err := os.MkdirAll(file, 0o755) - if err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(file, 0o755)) } _, _, err := executeActionCommand(cmd) - if tt.wantError && err == nil { - t.Fatalf("%q: expected error but got none", tt.name) - } - if err != nil { - if tt.wantError { - if tt.wantErrorMsg != "" && tt.wantErrorMsg != err.Error() { - t.Fatalf("Actual error '%s', not equal to expected error '%s'", err, tt.wantErrorMsg) - } - return + if tt.wantError { + require.Error(t, err, "%q: expected error but got none", tt.name) + if tt.wantErrorMsg != "" { + require.EqualErrorf(t, err, tt.wantErrorMsg, "Actual error '%s', not equal to expected error '%s'", err, tt.wantErrorMsg) } - t.Fatalf("%q reported error: %s", tt.name, err) - } - - ef := filepath.Join(outdir, tt.expectFile) - fi, err := os.Stat(ef) - if err != nil { - t.Errorf("%q: expected a file at %s. %s", tt.name, ef, err) - } - if fi.IsDir() != tt.expectDir { - t.Errorf("%q: expected directory=%t, but it's not.", tt.name, tt.expectDir) + } else { + require.NoError(t, err, "%q reported error", tt.name) + ef := filepath.Join(outdir, tt.expectFile) + fi, err := os.Stat(ef) + require.NoError(t, err, "%q: expected a file at %s.", tt.name, ef) + assert.Equal(t, tt.expectDir, fi.IsDir(), "%q: expected directory=%t, but it's not.", tt.name, tt.expectDir) } }) } @@ -362,9 +334,7 @@ func TestPullWithCredentialsCmd(t *testing.T) { })) defer srv2.Close() - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.LinkIndices()) // all flags will get "-d outdir" appended. tests := []struct { @@ -445,14 +415,10 @@ func TestPullWithCredentialsCmdOCIRegistry(t *testing.T) { defer srv.Stop() ociSrv, err := repotest.NewOCIServer(t, srv.Root()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) ociSrv.Run(t) - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.LinkIndices()) // all flags will get "-d outdir" appended. tests := []struct { @@ -517,9 +483,7 @@ func TestPullOCIWithTagAndDigest(t *testing.T) { defer srv.Stop() ociSrv, err := repotest.NewOCIServer(t, srv.Root()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) result := ociSrv.RunWithReturn(t) contentCache := t.TempDir() @@ -538,9 +502,7 @@ func TestPullOCIWithTagAndDigest(t *testing.T) { ) _, _, err = executeActionCommand(cmd) - if err != nil { - t.Fatalf("pull with tag+digest failed: %v", err) - } + require.NoError(t, err, "pull with tag+digest failed") // Verify the file was downloaded // When digest is present, the filename uses the digest format (e.g. chart@sha256-hex.tgz) @@ -548,12 +510,9 @@ func TestPullOCIWithTagAndDigest(t *testing.T) { if _, err := os.Stat(expectedFile); err != nil { // Try the digest-based filename; parse algorithm:hex to avoid fixed-offset assumptions algorithm, digestPart, ok := strings.Cut(result.PushedChart.Manifest.Digest, ":") - if !ok { - t.Fatalf("digest must be in algorithm:hex format, got %q", result.PushedChart.Manifest.Digest) - } + require.True(t, ok, "digest must be in algorithm:hex format, got %q", result.PushedChart.Manifest.Digest) expectedFile = filepath.Join(outdir, fmt.Sprintf("oci-dependent-chart@%s-%s.tgz", algorithm, digestPart)) - if _, err := os.Stat(expectedFile); err != nil { - t.Errorf("expected chart file not found: %v", err) - } + _, err := os.Stat(expectedFile) + assert.NoErrorf(t, err, "expected chart file not found") } } From fb8a62bf13cbc0ad78d0856678365b246b224997 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:56 +0200 Subject: [PATCH 474/571] chore(pkg): refactor: convert tests to testify assert/require part 18 #### Description refactor: convert tests to testify assert/require in pkg/cmd Signed-off-by: Matthieu MOREL --- pkg/action/package_test.go | 54 ++++----------- pkg/chart/v2/lint/rules/dependencies_test.go | 22 ++---- pkg/chart/v2/util/create_test.go | 71 ++++++-------------- pkg/cmd/plugin_uninstall_test.go | 66 +++++++----------- pkg/cmd/repo_remove_test.go | 62 ++++++----------- pkg/cmd/repo_update_test.go | 65 ++++++------------ pkg/kube/resource_test.go | 45 ++++--------- 7 files changed, 117 insertions(+), 268 deletions(-) diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go index 9984b508a..421f34b33 100644 --- a/pkg/action/package_test.go +++ b/pkg/action/package_test.go @@ -17,7 +17,6 @@ limitations under the License. package action import ( - "errors" "os" "path" "testing" @@ -35,18 +34,12 @@ func TestPassphraseFileFetcher(t *testing.T) { testPkg := NewPackage() fetcher, err := testPkg.passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) - if err != nil { - t.Fatal("Unable to create passphraseFileFetcher", err) - } + require.NoError(t, err, "Unable to create passphraseFileFetcher") passphrase, err := fetcher("key") - if err != nil { - t.Fatal("Unable to fetch passphrase") - } + require.NoError(t, err, "Unable to fetch passphrase") - if string(passphrase) != secret { - t.Errorf("Expected %s got %s", secret, string(passphrase)) - } + assert.Equal(t, secret, string(passphrase), "Expected %s got %s", secret, string(passphrase)) } func TestPassphraseFileFetcher_WithLineBreak(t *testing.T) { @@ -55,18 +48,12 @@ func TestPassphraseFileFetcher_WithLineBreak(t *testing.T) { testPkg := NewPackage() fetcher, err := testPkg.passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) - if err != nil { - t.Fatal("Unable to create passphraseFileFetcher", err) - } + require.NoError(t, err, "Unable to create passphraseFileFetcher") passphrase, err := fetcher("key") - if err != nil { - t.Fatal("Unable to fetch passphrase") - } + require.NoError(t, err, "Unable to fetch passphrase") - if string(passphrase) != secret { - t.Errorf("Expected %s got %s", secret, string(passphrase)) - } + assert.Equal(t, secret, string(passphrase), "Expected %s got %s", secret, string(passphrase)) } func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) { @@ -74,21 +61,16 @@ func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) { testPkg := NewPackage() stdin, err := os.CreateTemp(directory, "non-existing") - if err != nil { - t.Fatal("Unable to create test file", err) - } + require.NoError(t, err, "Unable to create test file") - if _, err := testPkg.passphraseFileFetcher("-", stdin); err == nil { - t.Error("Expected passphraseFileFetcher returning an error") - } + _, err = testPkg.passphraseFileFetcher("-", stdin) + assert.Error(t, err, "Expected passphraseFileFetcher returning an error") } func TestPassphraseFileFetcher_WithStdinAndMultipleFetches(t *testing.T) { testPkg := NewPackage() stdin, w, err := os.Pipe() - if err != nil { - t.Fatal("Unable to create pipe", err) - } + require.NoError(t, err, "Unable to create pipe") passphrase := "secret-from-stdin" @@ -99,18 +81,12 @@ func TestPassphraseFileFetcher_WithStdinAndMultipleFetches(t *testing.T) { for range 4 { fetcher, err := testPkg.passphraseFileFetcher("-", stdin) - if err != nil { - t.Errorf("Expected passphraseFileFetcher to not return an error, but got %v", err) - } + require.NoError(t, err, "Expected passphraseFileFetcher to not return an error") pass, err := fetcher("key") - if err != nil { - t.Errorf("Expected passphraseFileFetcher invocation to succeed, failed with %v", err) - } + require.NoError(t, err, "Expected passphraseFileFetcher invocation to succeed") - if string(pass) != string(passphrase) { - t.Errorf("Expected multiple passphrase fetch to return %q, got %q", passphrase, pass) - } + assert.Equal(t, string(passphrase), string(pass), "Expected multiple passphrase fetch to return %q, got %q", passphrase, pass) } } @@ -148,9 +124,7 @@ func TestValidateVersion(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := validateVersion(tt.args.ver); err != nil { - if !errors.Is(err, tt.wantErr) { - t.Errorf("Expected {%v}, got {%v}", tt.wantErr, err) - } + assert.ErrorIs(t, err, tt.wantErr) } }) } diff --git a/pkg/chart/v2/lint/rules/dependencies_test.go b/pkg/chart/v2/lint/rules/dependencies_test.go index c50fc4b2d..c800887bd 100644 --- a/pkg/chart/v2/lint/rules/dependencies_test.go +++ b/pkg/chart/v2/lint/rules/dependencies_test.go @@ -19,6 +19,9 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/chart/v2/lint/support" chartutil "helm.sh/helm/v4/pkg/chart/v2/util" @@ -62,18 +65,12 @@ func chartWithBadDependencies() chart.Chart { func TestValidateDependencyInChartsDir(t *testing.T) { c := chartWithBadDependencies() - - if err := validateDependencyInChartsDir(&c); err == nil { - t.Error("chart should have been flagged for missing deps in chart directory") - } + assert.Error(t, validateDependencyInChartsDir(&c), "chart should have been flagged for missing deps in chart directory") } func TestValidateDependencyInMetadata(t *testing.T) { c := chartWithBadDependencies() - - if err := validateDependencyInMetadata(&c); err == nil { - t.Error("chart should have been flagged for missing deps in chart metadata") - } + assert.Error(t, validateDependencyInMetadata(&c), "chart should have been flagged for missing deps in chart metadata") } func TestValidateDependenciesUnique(t *testing.T) { @@ -131,9 +128,7 @@ func TestValidateDependenciesUnique(t *testing.T) { } for _, tt := range tests { - if err := validateDependenciesUnique(&tt.chart); err == nil { - t.Error("chart should have been flagged for dependency shadowing") - } + assert.Error(t, validateDependenciesUnique(&tt.chart), "chart should have been flagged for dependency shadowing") } } @@ -141,10 +136,7 @@ func TestDependencies(t *testing.T) { tmp := t.TempDir() c := chartWithBadDependencies() - err := chartutil.SaveDir(&c, tmp) - if err != nil { - t.Fatal(err) - } + require.NoError(t, chartutil.SaveDir(&c, tmp)) linter := support.Linter{ChartDir: filepath.Join(tmp, c.Metadata.Name)} Dependencies(&linter) diff --git a/pkg/chart/v2/util/create_test.go b/pkg/chart/v2/util/create_test.go index 367b755d9..c85c4e3d4 100644 --- a/pkg/chart/v2/util/create_test.go +++ b/pkg/chart/v2/util/create_test.go @@ -22,6 +22,9 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/chart/v2/loader" ) @@ -30,20 +33,13 @@ func TestCreate(t *testing.T) { tdir := t.TempDir() c, err := Create("foo", tdir) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) dir := filepath.Join(tdir, "foo") mychart, err := loader.LoadDir(c) - if err != nil { - t.Fatalf("Failed to load newly created chart %q: %s", c, err) - } - - if mychart.Name() != "foo" { - t.Errorf("Expected name to be 'foo', got %q", mychart.Name()) - } + require.NoError(t, err, "Failed to load newly created chart %q", c) + assert.Equal(t, "foo", mychart.Name(), "Expected name to be 'foo', got %q", mychart.Name()) for _, f := range []string{ ChartfileName, @@ -58,9 +54,8 @@ func TestCreate(t *testing.T) { TestConnectionName, ValuesfileName, } { - if _, err := os.Stat(filepath.Join(dir, f)); err != nil { - t.Errorf("Expected %s file: %s", f, err) - } + _, err := os.Stat(filepath.Join(dir, f)) + assert.NoErrorf(t, err, "Expected %s file", f) } } @@ -74,38 +69,26 @@ func TestCreateFrom(t *testing.T) { } srcdir := "./testdata/frobnitz/charts/mariner" - if err := CreateFrom(cf, tdir, srcdir); err != nil { - t.Fatal(err) - } + require.NoError(t, CreateFrom(cf, tdir, srcdir)) dir := filepath.Join(tdir, "foo") c := filepath.Join(tdir, cf.Name) mychart, err := loader.LoadDir(c) - if err != nil { - t.Fatalf("Failed to load newly created chart %q: %s", c, err) - } - - if mychart.Name() != "foo" { - t.Errorf("Expected name to be 'foo', got %q", mychart.Name()) - } + require.NoError(t, err, "Failed to load newly created chart %q", c) + assert.Equal(t, "foo", mychart.Name(), "Expected name to be 'foo', got %q", mychart.Name()) for _, f := range []string{ ChartfileName, ValuesfileName, filepath.Join(TemplatesDir, "placeholder.tpl"), } { - if _, err := os.Stat(filepath.Join(dir, f)); err != nil { - t.Errorf("Expected %s file: %s", f, err) - } + _, err := os.Stat(filepath.Join(dir, f)) + require.NoErrorf(t, err, "Expected %s file", f) // Check each file to make sure has been replaced b, err := os.ReadFile(filepath.Join(dir, f)) - if err != nil { - t.Errorf("Unable to read file %s: %s", f, err) - } - if bytes.Contains(b, []byte("")) { - t.Errorf("File %s contains ", f) - } + require.NoError(t, err, "Unable to read file %s", f) + assert.Falsef(t, bytes.Contains(b, []byte("")), "File %s contains ", f) } } @@ -115,9 +98,8 @@ func TestCreate_Overwrite(t *testing.T) { var errlog bytes.Buffer - if _, err := Create("foo", tdir); err != nil { - t.Fatal(err) - } + _, err := Create("foo", tdir) + require.NoError(t, err) dir := filepath.Join(tdir, "foo") @@ -126,22 +108,13 @@ func TestCreate_Overwrite(t *testing.T) { // Now re-run the create Stderr = &errlog - if _, err := Create("foo", tdir); err != nil { - t.Fatal(err) - } + _, err = Create("foo", tdir) + require.NoError(t, err) data, err := os.ReadFile(tplname) - if err != nil { - t.Fatal(err) - } - - if string(data) == "FOO" { - t.Fatal("File that should have been modified was not.") - } - - if errlog.Len() == 0 { - t.Error("Expected warnings about overwriting files.") - } + require.NoError(t, err) + require.NotEqual(t, "FOO", string(data), "File that should have been modified was not.") + assert.NotEqual(t, 0, errlog.Len(), "Expected warnings about overwriting files.") } func TestValidateChartName(t *testing.T) { diff --git a/pkg/cmd/plugin_uninstall_test.go b/pkg/cmd/plugin_uninstall_test.go index f715b09c8..bd08f6f13 100644 --- a/pkg/cmd/plugin_uninstall_test.go +++ b/pkg/cmd/plugin_uninstall_test.go @@ -21,6 +21,9 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/internal/plugin" "helm.sh/helm/v4/internal/test/ensure" "helm.sh/helm/v4/pkg/cli" @@ -39,9 +42,7 @@ func TestPluginUninstallCleansUpVersionedFiles(t *testing.T) { // Create plugin directory pluginDir := filepath.Join(pluginsDir, pluginName) - if err := os.MkdirAll(pluginDir, 0o755); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(pluginDir, 0o755)) // Create plugin.yaml pluginYAML := `name: test-plugin @@ -49,31 +50,21 @@ version: 1.2.3 description: Test plugin command: $HELM_PLUGIN_DIR/test-plugin ` - if err := os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginYAML), 0o644); err != nil { - t.Fatal(err) - } + require.NoError(t, os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginYAML), 0o644)) // Create versioned tarball and provenance files tarballFile := filepath.Join(pluginsDir, "test-plugin-1.2.3.tgz") provFile := filepath.Join(pluginsDir, "test-plugin-1.2.3.tgz.prov") otherVersionTarball := filepath.Join(pluginsDir, "test-plugin-2.0.0.tgz") - if err := os.WriteFile(tarballFile, []byte("fake tarball"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(provFile, []byte("fake provenance"), 0o644); err != nil { - t.Fatal(err) - } + require.NoError(t, os.WriteFile(tarballFile, []byte("fake tarball"), 0o644)) + require.NoError(t, os.WriteFile(provFile, []byte("fake provenance"), 0o644)) // Create another version that should NOT be removed - if err := os.WriteFile(otherVersionTarball, []byte("other version"), 0o644); err != nil { - t.Fatal(err) - } + require.NoError(t, os.WriteFile(otherVersionTarball, []byte("other version"), 0o644)) // Load the plugin p, err := plugin.LoadDir(pluginDir) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Create a test uninstall function that uses our test settings testUninstallPlugin := func(plugin plugin.Plugin) error { @@ -112,35 +103,26 @@ command: $HELM_PLUGIN_DIR/test-plugin } // Verify files exist before uninstall - if _, err := os.Stat(tarballFile); os.IsNotExist(err) { - t.Fatal("tarball file should exist before uninstall") - } - if _, err := os.Stat(provFile); os.IsNotExist(err) { - t.Fatal("provenance file should exist before uninstall") - } - if _, err := os.Stat(otherVersionTarball); os.IsNotExist(err) { - t.Fatal("other version tarball should exist before uninstall") - } + _, err = os.Stat(tarballFile) + require.False(t, os.IsNotExist(err), "tarball file should exist before uninstall") + _, err = os.Stat(provFile) + require.False(t, os.IsNotExist(err), "provenance file should exist before uninstall") + _, err = os.Stat(otherVersionTarball) + require.False(t, os.IsNotExist(err), "other version tarball should exist before uninstall") // Uninstall the plugin - if err := testUninstallPlugin(p); err != nil { - t.Fatal(err) - } + require.NoError(t, testUninstallPlugin(p)) // Verify plugin directory is removed - if _, err := os.Stat(pluginDir); !os.IsNotExist(err) { - t.Error("plugin directory should be removed") - } + _, err = os.Stat(pluginDir) + assert.True(t, os.IsNotExist(err), "plugin directory should be removed") // Verify only exact version files are removed - if _, err := os.Stat(tarballFile); !os.IsNotExist(err) { - t.Error("versioned tarball file should be removed") - } - if _, err := os.Stat(provFile); !os.IsNotExist(err) { - t.Error("versioned provenance file should be removed") - } + _, err = os.Stat(tarballFile) + assert.True(t, os.IsNotExist(err), "versioned tarball file should be removed") + _, err = os.Stat(provFile) + assert.True(t, os.IsNotExist(err), "versioned provenance file should be removed") // Verify other version files are NOT removed - if _, err := os.Stat(otherVersionTarball); os.IsNotExist(err) { - t.Error("other version tarball should NOT be removed") - } + _, err = os.Stat(otherVersionTarball) + assert.False(t, os.IsNotExist(err), "other version tarball should NOT be removed") } diff --git a/pkg/cmd/repo_remove_test.go b/pkg/cmd/repo_remove_test.go index 3b0072a68..7d252c35d 100644 --- a/pkg/cmd/repo_remove_test.go +++ b/pkg/cmd/repo_remove_test.go @@ -21,9 +21,11 @@ import ( "fmt" "os" "path/filepath" - "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/helmpath" "helm.sh/helm/v4/pkg/repo/v1" "helm.sh/helm/v4/pkg/repo/v1/repotest" @@ -49,41 +51,29 @@ func TestRepoRemove(t *testing.T) { repoCache: rootDir, } - if err := rmOpts.run(os.Stderr); err == nil { - t.Errorf("Expected error removing %s, but did not get one.", testRepoName) - } + require.Errorf(t, rmOpts.run(os.Stderr), "Expected error removing %s, but did not get one.", testRepoName) o := &repoAddOptions{ name: testRepoName, url: ts.URL(), repoFile: repoFile, } - if err := o.run(os.Stderr); err != nil { - t.Error(err) - } + require.NoError(t, o.run(os.Stderr)) cacheIndexFile, cacheChartsFile := createCacheFiles(rootDir, testRepoName) // Reset the buffer before running repo remove b.Reset() - if err := rmOpts.run(b); err != nil { - t.Errorf("Error removing %s from repositories", testRepoName) - } - if !strings.Contains(b.String(), "has been removed") { - t.Errorf("Unexpected output: %s", b.String()) - } + require.NoErrorf(t, rmOpts.run(b), "Error removing %s from repositories", testRepoName) + assert.Contains(t, b.String(), "has been removed", "Unexpected output: %s", b.String()) testCacheFiles(t, cacheIndexFile, cacheChartsFile, testRepoName) f, err := repo.LoadFile(repoFile) - if err != nil { - t.Error(err) - } + require.NoError(t, err) - if f.Has(testRepoName) { - t.Errorf("%s was not successfully removed from repositories list", testRepoName) - } + assert.Falsef(t, f.Has(testRepoName), "%s was not successfully removed from repositories list", testRepoName) // Test removal of multiple repos in one go var testRepoNames = []string{"foo", "bar", "baz"} @@ -97,9 +87,7 @@ func TestRepoRemove(t *testing.T) { repoFile: repoFile, } - if err := o.run(os.Stderr); err != nil { - t.Error(err) - } + require.NoError(t, o.run(os.Stderr)) cacheIndex, cacheChart := createCacheFiles(rootDir, repoName) cacheFiles[repoName] = []string{cacheIndex, cacheChart} @@ -116,23 +104,15 @@ func TestRepoRemove(t *testing.T) { b.Reset() // Run repo remove command - if err := multiRmOpts.run(b); err != nil { - t.Errorf("Error removing list of repos from repositories: %q", testRepoNames) - } + require.NoErrorf(t, multiRmOpts.run(b), "Error removing list of repos from repositories: %q", testRepoNames) // Check that stuff were removed - if !strings.Contains(b.String(), "has been removed") { - t.Errorf("Unexpected output: %s", b.String()) - } + assert.Contains(t, b.String(), "has been removed", "Unexpected output: %s", b.String()) for _, repoName := range testRepoNames { f, err := repo.LoadFile(repoFile) - if err != nil { - t.Error(err) - } - if f.Has(repoName) { - t.Errorf("%s was not successfully removed from repositories list", repoName) - } + require.NoError(t, err) + assert.Falsef(t, f.Has(repoName), "%s was not successfully removed from repositories list", repoName) cacheIndex := cacheFiles[repoName][0] cacheChart := cacheFiles[repoName][1] testCacheFiles(t, cacheIndex, cacheChart, repoName) @@ -153,12 +133,10 @@ func createCacheFiles(rootDir string, repoName string) (cacheIndexFile string, c func testCacheFiles(t *testing.T, cacheIndexFile string, cacheChartsFile string, repoName string) { t.Helper() - if _, err := os.Stat(cacheIndexFile); err == nil { - t.Errorf("Error cache index file was not removed for repository %s", repoName) - } - if _, err := os.Stat(cacheChartsFile); err == nil { - t.Errorf("Error cache chart file was not removed for repository %s", repoName) - } + _, err := os.Stat(cacheIndexFile) + require.Errorf(t, err, "Error cache index file was not removed for repository %s", repoName) + _, err = os.Stat(cacheChartsFile) + assert.Errorf(t, err, "Error cache chart file was not removed for repository %s", repoName) } func TestRepoRemoveCompletion(t *testing.T) { @@ -183,9 +161,7 @@ func TestRepoRemoveCompletion(t *testing.T) { repoFile: repoFile, } - if err := o.run(os.Stderr); err != nil { - t.Error(err) - } + require.NoError(t, o.run(os.Stderr)) } repoSetup := fmt.Sprintf("--repository-config %s --repository-cache %s", repoFile, repoCache) diff --git a/pkg/cmd/repo_update_test.go b/pkg/cmd/repo_update_test.go index 7aa4d414f..ccd8b89c6 100644 --- a/pkg/cmd/repo_update_test.go +++ b/pkg/cmd/repo_update_test.go @@ -24,6 +24,9 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/internal/test/ensure" "helm.sh/helm/v4/pkg/getter" "helm.sh/helm/v4/pkg/repo/v1" @@ -44,9 +47,7 @@ func TestUpdateCmd(t *testing.T) { update: updater, repoFile: "testdata/repositories.yaml", } - if err := o.run(&out); err != nil { - t.Fatal(err) - } + require.NoError(t, o.run(&out)) if got := out.String(); !strings.Contains(got, "charts") || !strings.Contains(got, "firstexample") || @@ -70,9 +71,7 @@ func TestUpdateCmdMultiple(t *testing.T) { repoFile: "testdata/repositories.yaml", names: []string{"firstexample", "charts"}, } - if err := o.run(&out); err != nil { - t.Fatal(err) - } + require.NoError(t, o.run(&out)) if got := out.String(); !strings.Contains(got, "charts") || !strings.Contains(got, "firstexample") || @@ -96,9 +95,7 @@ func TestUpdateCmdInvalid(t *testing.T) { repoFile: "testdata/repositories.yaml", names: []string{"firstexample", "invalid"}, } - if err := o.run(&out); err == nil { - t.Fatal("expected error but did not get one") - } + require.Error(t, o.run(&out), "expected error but did not get one") } func TestUpdateCustomCacheCmd(t *testing.T) { @@ -119,12 +116,9 @@ func TestUpdateCustomCacheCmd(t *testing.T) { repoCache: cachePath, } b := io.Discard - if err := o.run(b); err != nil { - t.Fatal(err) - } - if _, err := os.Stat(filepath.Join(cachePath, "test-index.yaml")); err != nil { - t.Fatalf("error finding created index file in custom cache: %v", err) - } + require.NoError(t, o.run(b)) + _, err := os.Stat(filepath.Join(cachePath, "test-index.yaml")) + require.NoErrorf(t, err, "error finding created index file in custom cache") } func TestUpdateCharts(t *testing.T) { @@ -140,20 +134,14 @@ func TestUpdateCharts(t *testing.T) { Name: "charts", URL: ts.URL(), }, getter.All(settings)) - if err != nil { - t.Error(err) - } + require.NoError(t, err) b := bytes.NewBuffer(nil) updateCharts([]*repo.ChartRepository{r}, b) got := b.String() - if strings.Contains(got, "Unable to get an update") { - t.Errorf("Failed to get a repo: %q", got) - } - if !strings.Contains(got, "Update Complete.") { - t.Error("Update was not successful") - } + assert.NotContains(t, got, "Unable to get an update", "Failed to get a repo: %q", got) + assert.Contains(t, got, "Update Complete.", "Update was not successful") } func TestRepoUpdateFileCompletion(t *testing.T) { @@ -176,37 +164,22 @@ func TestUpdateChartsFailWithError(t *testing.T) { Name: "charts", URL: invalidURL, }, getter.All(settings)) - if err != nil { - t.Error(err) - } + require.NoError(t, err) r2, err := repo.NewChartRepository(&repo.Entry{ Name: "charts", URL: invalidURL, }, getter.All(settings)) - if err != nil { - t.Error(err) - } + require.NoError(t, err) b := bytes.NewBuffer(nil) err = updateCharts([]*repo.ChartRepository{r1, r2}, b) - if err == nil { - t.Error("Repo update should return error because update of repository fails and 'fail-on-repo-update-fail' flag set") - return - } + require.Error(t, err, "Repo update should return error because update of repository fails and 'fail-on-repo-update-fail' flag set") var expectedErr = "failed to update the following repositories" var receivedErr = err.Error() - if !strings.Contains(receivedErr, expectedErr) { - t.Errorf("Expected error (%s) but got (%s) instead", expectedErr, receivedErr) - } - if !strings.Contains(receivedErr, invalidURL) { - t.Errorf("Expected invalid URL (%s) in error message but got (%s) instead", invalidURL, receivedErr) - } + require.ErrorContains(t, err, expectedErr, "Expected error (%s) but got (%s) instead", expectedErr, receivedErr) + require.ErrorContains(t, err, invalidURL, "Expected invalid URL (%s) in error message but got (%s) instead", invalidURL, receivedErr) 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 and should return error message because 'fail-on-repo-update-fail' flag set") - } + assert.Contains(t, got, "Unable to get an update", "Repo should have failed update but instead got: %q", got) + assert.NotContains(t, got, "Update Complete.", "Update was not successful and should return error message because 'fail-on-repo-update-fail' flag set") } diff --git a/pkg/kube/resource_test.go b/pkg/kube/resource_test.go index 283b44945..ca41a06ed 100644 --- a/pkg/kube/resource_test.go +++ b/pkg/kube/resource_test.go @@ -19,6 +19,7 @@ package kube import ( "testing" + "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/cli-runtime/pkg/resource" @@ -37,27 +38,17 @@ func TestResourceList(t *testing.T) { r1 = []*resource.Info{info("foo"), info("bar")} r2 = []*resource.Info{info("bar")} - if r1.Get(info("bar")).Mapping.Resource.Resource != "pod" { - t.Error("expected get pod") - } + assert.Equal(t, "pod", r1.Get(info("bar")).Mapping.Resource.Resource, "expected get pod") diff := r1.Difference(r2) - if len(diff) != 1 { - t.Error("expected 1 result") - } + assert.Len(t, diff, 1, "expected 1 result") - if !diff.Contains(info("foo")) { - t.Error("expected diff to return foo") - } + assert.True(t, diff.Contains(info("foo")), "expected diff to return foo") inter := r1.Intersect(r2) - if len(inter) != 1 { - t.Error("expected 1 result") - } + assert.Len(t, inter, 1, "expected 1 result") - if !inter.Contains(info("bar")) { - t.Error("expected intersect to return bar") - } + assert.True(t, inter.Contains(info("bar")), "expected intersect to return bar") } func TestIsMatchingInfo(t *testing.T) { @@ -66,35 +57,23 @@ func TestIsMatchingInfo(t *testing.T) { gvkDiffGroup := schema.GroupVersionKind{Group: "diff", Version: "version1", Kind: "pod"} resourceInfoDiffGroup := resource.Info{Name: "name1", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvkDiffGroup}} - if isMatchingInfo(&resourceInfo, &resourceInfoDiffGroup) { - t.Error("expected resources not equal") - } + assert.False(t, isMatchingInfo(&resourceInfo, &resourceInfoDiffGroup), "expected resources not equal") gvkDiffVersion := schema.GroupVersionKind{Group: "group1", Version: "diff", Kind: "pod"} resourceInfoDiffVersion := resource.Info{Name: "name1", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvkDiffVersion}} - if !isMatchingInfo(&resourceInfo, &resourceInfoDiffVersion) { - t.Error("expected resources with different versions but same group and kind to be equal") - } + assert.True(t, isMatchingInfo(&resourceInfo, &resourceInfoDiffVersion), "expected resources with different versions but same group and kind to be equal") gvkDiffKind := schema.GroupVersionKind{Group: "group1", Version: "version1", Kind: "deployment"} resourceInfoDiffKind := resource.Info{Name: "name1", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvkDiffKind}} - if isMatchingInfo(&resourceInfo, &resourceInfoDiffKind) { - t.Error("expected resources not equal") - } + assert.False(t, isMatchingInfo(&resourceInfo, &resourceInfoDiffKind), "expected resources not equal") resourceInfoDiffName := resource.Info{Name: "diff", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvk}} - if isMatchingInfo(&resourceInfo, &resourceInfoDiffName) { - t.Error("expected resources not equal") - } + assert.False(t, isMatchingInfo(&resourceInfo, &resourceInfoDiffName), "expected resources not equal") resourceInfoDiffNamespace := resource.Info{Name: "name1", Namespace: "diff", Mapping: &meta.RESTMapping{GroupVersionKind: gvk}} - if isMatchingInfo(&resourceInfo, &resourceInfoDiffNamespace) { - t.Error("expected resources not equal") - } + assert.False(t, isMatchingInfo(&resourceInfo, &resourceInfoDiffNamespace), "expected resources not equal") gvkEqual := schema.GroupVersionKind{Group: "group1", Version: "version1", Kind: "pod"} resourceInfoEqual := resource.Info{Name: "name1", Namespace: "namespace1", Mapping: &meta.RESTMapping{GroupVersionKind: gvkEqual}} - if !isMatchingInfo(&resourceInfo, &resourceInfoEqual) { - t.Error("expected resources to be equal") - } + assert.True(t, isMatchingInfo(&resourceInfo, &resourceInfoEqual), "expected resources to be equal") } From 1d4665e54c28a41a5631c19f3bff14ef3d4e058f Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:56 +0200 Subject: [PATCH 475/571] chore(pkg): refactor: convert tests to testify assert/require part 20 #### Description refactor: convert tests to testify assert/require in pkg/cmd Signed-off-by: Matthieu MOREL --- pkg/chart/common/util/values_test.go | 52 ++++++-------- pkg/chart/loader/archive/archive_test.go | 29 +++----- pkg/chart/v2/lint/lint_test.go | 43 +++--------- pkg/cmd/list_test.go | 46 +++---------- pkg/cmd/package_test.go | 44 +++++------- pkg/cmd/release_testing_test.go | 12 ++-- pkg/cmd/source_date_epoch_test.go | 44 ++++-------- .../chart_downloader_windows_test.go | 67 +++++++++---------- pkg/kube/wait_test.go | 36 +++------- pkg/registry/client_tls_test.go | 23 +++---- pkg/release/v1/info_test.go | 43 +++++------- 11 files changed, 151 insertions(+), 288 deletions(-) diff --git a/pkg/chart/common/util/values_test.go b/pkg/chart/common/util/values_test.go index 1882fd0c4..e87a69237 100644 --- a/pkg/chart/common/util/values_test.go +++ b/pkg/chart/common/util/values_test.go @@ -20,6 +20,9 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" ) @@ -61,42 +64,26 @@ func TestToRenderValues(t *testing.T) { } res, err := ToRenderValuesWithSchemaValidation(c, overrideValues, o, nil, false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Ensure that the top-level values are all set. metamap := res["Chart"].(map[string]any) - if name := metamap["Name"]; name.(string) != "test" { - t.Errorf("Expected chart name 'test', got %q", name) - } + name := metamap["Name"] + assert.Equalf(t, "test", name.(string), "Expected chart name 'test', got %q", name) relmap := res["Release"].(map[string]any) - if name := relmap["Name"]; name.(string) != "Seven Voyages" { - t.Errorf("Expected release name 'Seven Voyages', got %q", name) - } - if namespace := relmap["Namespace"]; namespace.(string) != "default" { - t.Errorf("Expected namespace 'default', got %q", namespace) - } - if revision := relmap["Revision"]; revision.(int) != 1 { - t.Errorf("Expected revision '1', got %d", revision) - } - if relmap["IsUpgrade"].(bool) { - t.Error("Expected upgrade to be false.") - } - if !relmap["IsInstall"].(bool) { - t.Error("Expected install to be true.") - } - if !res["Capabilities"].(*common.Capabilities).APIVersions.Has("v1") { - t.Error("Expected Capabilities to have v1 as an API") - } - if res["Capabilities"].(*common.Capabilities).KubeVersion.Major != "1" { - t.Error("Expected Capabilities to have a Kube version") - } + name = relmap["Name"] + assert.Equalf(t, "Seven Voyages", name.(string), "Expected release name 'Seven Voyages', got %q", name) + namespace := relmap["Namespace"] + assert.Equalf(t, "default", namespace.(string), "Expected namespace 'default', got %q", namespace) + revision := relmap["Revision"] + assert.Equalf(t, 1, revision.(int), "Expected revision '1', got %d", revision) + assert.False(t, relmap["IsUpgrade"].(bool), "Expected upgrade to be false.") + assert.True(t, relmap["IsInstall"].(bool), "Expected install to be true.") + assert.True(t, res["Capabilities"].(*common.Capabilities).APIVersions.Has("v1"), "Expected Capabilities to have v1 as an API") + assert.Equal(t, "1", res["Capabilities"].(*common.Capabilities).KubeVersion.Major, "Expected Capabilities to have a Kube version") vals := res["Values"].(common.Values) - if vals["name"] != "Haroun" { - t.Errorf("Expected 'Haroun', got %q (%v)", vals["name"], vals) - } + assert.Equal(t, "Haroun", vals["name"], "Expected 'Haroun', got %q (%v)", vals["name"], vals) where := vals["where"].(map[string]any) expects := map[string]string{ "city": "Baghdad", @@ -104,8 +91,7 @@ func TestToRenderValues(t *testing.T) { "title": "caliph", } for field, expect := range expects { - if got := where[field]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, where) - } + got := where[field] + assert.Equalf(t, got, expect, "Expected %q, got %q (%v)", expect, got, where) } } diff --git a/pkg/chart/loader/archive/archive_test.go b/pkg/chart/loader/archive/archive_test.go index 2fe09e9b2..3c3d336c3 100644 --- a/pkg/chart/loader/archive/archive_test.go +++ b/pkg/chart/loader/archive/archive_test.go @@ -21,6 +21,8 @@ import ( "bytes" "compress/gzip" "testing" + + "github.com/stretchr/testify/require" ) func TestLoadArchiveFiles(t *testing.T) { @@ -34,9 +36,7 @@ func TestLoadArchiveFiles(t *testing.T) { generate: func(_ *tar.Writer) {}, check: func(t *testing.T, _ []*BufferedFile, err error) { t.Helper() - if err.Error() != "no files in chart archive" { - t.Fatalf(`expected "no files in chart archive", got [%#v]`, err) - } + require.EqualError(t, err, "no files in chart archive") }, }, { @@ -44,32 +44,21 @@ func TestLoadArchiveFiles(t *testing.T) { generate: func(w *tar.Writer) { // simulate the presence of a `pax_global_header` file like you would get when // processing a GitHub release archive. - err := w.WriteHeader(&tar.Header{ + require.NoError(t, w.WriteHeader(&tar.Header{ Typeflag: tar.TypeXGlobalHeader, Name: "pax_global_header", - }) - if err != nil { - t.Fatal(err) - } + })) // we need to have at least one file, otherwise we'll get the "no files in chart archive" error - err = w.WriteHeader(&tar.Header{ + require.NoError(t, w.WriteHeader(&tar.Header{ Typeflag: tar.TypeReg, Name: "dir/empty", - }) - if err != nil { - t.Fatal(err) - } + })) }, check: func(t *testing.T, files []*BufferedFile, err error) { t.Helper() - if err != nil { - t.Fatalf(`got unwanted error [%#v] for tar file with pax_global_header content`, err) - } - - if len(files) != 1 { - t.Fatalf(`expected to get one file but got [%v]`, files) - } + require.NoErrorf(t, err, `got unwanted error for tar file with pax_global_header content`) + require.Lenf(t, files, 1, `expected to get one file but got [%v]`, files) }, }, } diff --git a/pkg/chart/v2/lint/lint_test.go b/pkg/chart/v2/lint/lint_test.go index 85960131b..1782c01d4 100644 --- a/pkg/chart/v2/lint/lint_test.go +++ b/pkg/chart/v2/lint/lint_test.go @@ -97,34 +97,22 @@ func TestBadChart(t *testing.T) { func TestInvalidYaml(t *testing.T) { var values map[string]any m := RunAll(badYamlFileDir, values, namespace).Messages - if len(m) != 1 { - t.Fatalf("All didn't fail with expected errors, got %#v", m) - } - if !strings.Contains(m[0].Err.Error(), "deliberateSyntaxError") { - t.Error("All didn't have the error for deliberateSyntaxError") - } + require.Len(t, m, 1, "All didn't fail with expected errors, got %#v", m) + assert.ErrorContains(t, m[0].Err, "deliberateSyntaxError", "All didn't have the error for deliberateSyntaxError") } func TestInvalidChartYaml(t *testing.T) { var values map[string]any m := RunAll(invalidChartFileDir, values, namespace).Messages - if len(m) != 2 { - t.Fatalf("All didn't fail with expected errors, got %#v", m) - } - if !strings.Contains(m[0].Err.Error(), "failed to strictly parse chart metadata file") { - t.Error("All didn't have the error for duplicate YAML keys") - } + require.Len(t, m, 2, "All didn't fail with expected errors, got %#v", m) + assert.ErrorContains(t, m[0].Err, "failed to strictly parse chart metadata file", "All didn't have the error for duplicate YAML keys") } func TestBadValues(t *testing.T) { var values map[string]any m := RunAll(badValuesFileDir, values, namespace).Messages - if len(m) < 1 { - t.Fatalf("All didn't fail with expected errors, got %#v", m) - } - if !strings.Contains(m[0].Err.Error(), "unable to parse YAML") { - t.Errorf("All didn't have the error for invalid key format: %s", m[0].Err) - } + require.GreaterOrEqualf(t, len(m), 1, "All didn't fail with expected errors, got %#v", m) + assert.ErrorContains(t, m[0].Err, "unable to parse YAML", "All didn't have the error for invalid key format: %s", m[0].Err) } func TestBadCrdFile(t *testing.T) { @@ -154,11 +142,7 @@ func TestHelmCreateChart(t *testing.T) { dir := t.TempDir() createdChart, err := chartutil.Create("testhelmcreatepasseslint", dir) - if err != nil { - t.Error(err) - // Fatal is bad because of the defer. - return - } + require.NoError(t, err) // Note: we test with strict=true here, even though others have // strict = false. @@ -182,10 +166,7 @@ func TestHelmCreateChart(t *testing.T) { // of the `--set` flag. func TestHelmCreateChart_CheckDeprecatedWarnings(t *testing.T) { createdChart, err := chartutil.Create("checkdeprecatedwarnings", t.TempDir()) - if err != nil { - t.Error(err) - return - } + require.NoError(t, err) // Add values to enable hpa, and ingress which are disabled by default. // This is the equivalent of: @@ -238,11 +219,7 @@ func TestMalformedTemplate(t *testing.T) { case <-c: t.Fatal("lint malformed template timeout") case <-ch: - if len(m) != 1 { - t.Fatalf("All didn't fail with expected errors, got %#v", m) - } - if !strings.Contains(m[0].Err.Error(), "invalid character '{'") { - t.Error("All didn't have the error for invalid character '{'") - } + require.Len(t, m, 1, "All didn't fail with expected errors, got %#v", m) + assert.ErrorContains(t, m[0].Err, "invalid character '{'", "All didn't have the error for invalid character '{'") } } diff --git a/pkg/cmd/list_test.go b/pkg/cmd/list_test.go index f45616250..41426fedb 100644 --- a/pkg/cmd/list_test.go +++ b/pkg/cmd/list_test.go @@ -426,20 +426,9 @@ func TestReleaseListWriterMethods(t *testing.T) { var buf []byte out := &bytesWriter{buf: &buf} - err := writer.WriteJSON(out) - if err != nil { - t.Errorf("WriteJSON failed: %v", err) - } - - err = writer.WriteYAML(out) - if err != nil { - t.Errorf("WriteYAML failed: %v", err) - } - - err = writer.WriteTable(out) - if err != nil { - t.Errorf("WriteTable failed: %v", err) - } + require.NoError(t, writer.WriteJSON(out), "WriteJSON failed") + require.NoError(t, writer.WriteYAML(out), "WriteYAML failed") + assert.NoError(t, writer.WriteTable(out), "WriteTable failed") }) } @@ -448,20 +437,9 @@ func TestReleaseListWriterMethods(t *testing.T) { var buf []byte out := &bytesWriter{buf: &buf} - err := writer.WriteJSON(out) - if err != nil { - t.Errorf("WriteJSON failed: %v", err) - } - - err = writer.WriteYAML(out) - if err != nil { - t.Errorf("WriteYAML failed: %v", err) - } - - err = writer.WriteTable(out) - if err != nil { - t.Errorf("WriteTable failed: %v", err) - } + require.NoError(t, writer.WriteJSON(out), "WriteJSON failed") + require.NoError(t, writer.WriteYAML(out), "WriteYAML failed") + assert.NoError(t, writer.WriteTable(out), "WriteTable failed") } func TestFilterReleases(t *testing.T) { @@ -512,9 +490,7 @@ func TestFilterReleases(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := filterReleases(tt.releases, tt.ignoredReleaseNames) - if len(result) != tt.expectedCount { - t.Errorf("Expected %d releases, got %d", tt.expectedCount, len(result)) - } + assert.Len(t, result, tt.expectedCount, "Expected %d releases, got %d", tt.expectedCount, len(result)) }) } } @@ -603,13 +579,9 @@ func TestListStatusMapping(t *testing.T) { } writer := newReleaseListWriter(releaseFixture, "", false, false) - if len(writer.releases) != 1 { - t.Errorf("Expected 1 release, got %d", len(writer.releases)) - } + assert.Len(t, writer.releases, 1, "Expected 1 release, got %d", len(writer.releases)) - if writer.releases[0].Status != tc.status.String() { - t.Errorf("Expected status %s, got %s", tc.status.String(), writer.releases[0].Status) - } + assert.Equal(t, tc.status.String(), writer.releases[0].Status, "Expected status %s, got %s", tc.status.String(), writer.releases[0].Status) }) } } diff --git a/pkg/cmd/package_test.go b/pkg/cmd/package_test.go index 5705c9b9e..d78f5d88d 100644 --- a/pkg/cmd/package_test.go +++ b/pkg/cmd/package_test.go @@ -23,6 +23,9 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/internal/test/ensure" chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/chart/v2/loader" @@ -105,18 +108,14 @@ func TestPackage(t *testing.T) { } origDir, err := os.Getwd() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Chdir(t.TempDir()) ensure.HelmHome(t) - if err := os.MkdirAll("toot", 0o777); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll("toot", 0o777)) // This is an unfortunate byproduct of the tmpdir if v, ok := tt.flags["keyring"]; ok && v != "" { @@ -140,6 +139,7 @@ func TestPackage(t *testing.T) { } } _, _, err = executeActionCommand(strings.Join(cmd, " ")) + if err != nil { if tt.err && re.MatchString(err.Error()) { return @@ -148,19 +148,15 @@ func TestPackage(t *testing.T) { } if tt.hasfile != "" { - if fi, err := os.Stat(tt.hasfile); err != nil { - t.Errorf("%q: expected file %q, got err %q", tt.name, tt.hasfile, err) - } else if fi.Size() == 0 { - t.Errorf("%q: file %q has zero bytes.", tt.name, tt.hasfile) - } + fi, err := os.Stat(tt.hasfile) + require.NoErrorf(t, err, "%q: expected file %q", tt.name, tt.hasfile) + assert.NotEqualf(t, 0, fi.Size(), "%q: file %q has zero bytes.", tt.name, tt.hasfile) } if v, ok := tt.flags["sign"]; ok && v == "1" { - if fi, err := os.Stat(tt.hasfile + ".prov"); err != nil { - t.Errorf("%q: expected provenance file", tt.name) - } else if fi.Size() == 0 { - t.Errorf("%q: provenance file is empty", tt.name) - } + fi, err := os.Stat(tt.hasfile + ".prov") + require.NoErrorf(t, err, "%q: expected provenance file", tt.name) + assert.NotEqualf(t, 0, fi.Size(), "%q: provenance file is empty", tt.name) } }) } @@ -178,18 +174,12 @@ func TestSetAppVersion(t *testing.T) { t.Fatal(err) } chartPath := filepath.Join(dir, "alpine-0.1.0.tgz") - if fi, err := os.Stat(chartPath); err != nil { - t.Errorf("expected file %q, got err %q", chartPath, err) - } else if fi.Size() == 0 { - t.Errorf("file %q has zero bytes.", chartPath) - } + fi, err := os.Stat(chartPath) + require.NoErrorf(t, err, "expected file %q", chartPath) + assert.NotEqualf(t, 0, fi.Size(), "file %q has zero bytes.", chartPath) ch, err = loader.Load(chartPath) - if err != nil { - t.Fatalf("unexpected error loading packaged chart: %v", err) - } - if ch.Metadata.AppVersion != expectedAppVersion { - t.Errorf("expected app-version %q, found %q", expectedAppVersion, ch.Metadata.AppVersion) - } + require.NoError(t, err, "unexpected error loading packaged chart") + assert.Equal(t, expectedAppVersion, ch.Metadata.AppVersion, "expected app-version %q, found %q", expectedAppVersion, ch.Metadata.AppVersion) } func TestPackageFileCompletion(t *testing.T) { diff --git a/pkg/cmd/release_testing_test.go b/pkg/cmd/release_testing_test.go index fdb5df1e9..a39816843 100644 --- a/pkg/cmd/release_testing_test.go +++ b/pkg/cmd/release_testing_test.go @@ -19,9 +19,11 @@ package cmd import ( "bytes" "io" - "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/action" "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" @@ -71,11 +73,7 @@ func TestReleaseTestNotesHandling(t *testing.T) { cmd1 := newReleaseTestCmd(actionConfig, &buf1) cmd1.SetArgs([]string{"test-release"}) err1 := cmd1.Execute() - if err1 != nil { - t.Fatalf("Unexpected error for default test: %v", err1) - } + require.NoError(t, err1, "Unexpected error for default test") output1 := buf1.String() - if strings.Contains(output1, "NOTES:") { - t.Errorf("Expected notes to be hidden by default, but found NOTES section in output: %s", output1) - } + assert.NotContains(t, output1, "NOTES:", "Expected notes to be hidden by default, but found NOTES section in output: %s", output1) } diff --git a/pkg/cmd/source_date_epoch_test.go b/pkg/cmd/source_date_epoch_test.go index 719de7c90..fa64c08b0 100644 --- a/pkg/cmd/source_date_epoch_test.go +++ b/pkg/cmd/source_date_epoch_test.go @@ -19,64 +19,48 @@ package cmd import ( "testing" "time" + + "github.com/stretchr/testify/require" ) func TestSourceDateEpochFromEnv(t *testing.T) { t.Setenv("SOURCE_DATE_EPOCH", "1609459200") got, err := sourceDateEpochFromEnv() - if err != nil { - t.Fatalf("sourceDateEpochFromEnv() error: %v", err) - } - if got == nil { - t.Fatal("expected non-nil epoch") - } + require.NoError(t, err, "sourceDateEpochFromEnv()") + require.NotNil(t, got, "expected non-nil epoch") want := time.Unix(1609459200, 0).UTC() - if !got.Equal(want) { - t.Fatalf("expected %v, got %v", want, *got) - } + require.Truef(t, got.Equal(want), "expected %v, got %v", want, *got) } func TestSourceDateEpochFromEnvUnset(t *testing.T) { t.Setenv("SOURCE_DATE_EPOCH", "") got, err := sourceDateEpochFromEnv() - if err != nil { - t.Fatalf("sourceDateEpochFromEnv() error: %v", err) - } - if got != nil { - t.Fatalf("expected nil epoch, got %v", *got) - } + require.NoError(t, err, "sourceDateEpochFromEnv()") + require.Nil(t, got, "expected nil epoch") } func TestSourceDateEpochFromEnvInvalid(t *testing.T) { t.Setenv("SOURCE_DATE_EPOCH", "not-a-number") - if _, err := sourceDateEpochFromEnv(); err == nil { - t.Fatal("expected error for invalid SOURCE_DATE_EPOCH") - } + _, err := sourceDateEpochFromEnv() + require.Error(t, err, "expected error for invalid SOURCE_DATE_EPOCH") } func TestSourceDateEpochFromEnvNegative(t *testing.T) { t.Setenv("SOURCE_DATE_EPOCH", "-1") - if _, err := sourceDateEpochFromEnv(); err == nil { - t.Fatal("expected error for negative SOURCE_DATE_EPOCH") - } + _, err := sourceDateEpochFromEnv() + require.Error(t, err, "expected error for negative SOURCE_DATE_EPOCH") } func TestSourceDateEpochFromEnvZero(t *testing.T) { t.Setenv("SOURCE_DATE_EPOCH", "0") got, err := sourceDateEpochFromEnv() - if err != nil { - t.Fatalf("sourceDateEpochFromEnv() error: %v", err) - } - if got == nil { - t.Fatal("expected non-nil epoch") - } + require.NoError(t, err, "sourceDateEpochFromEnv() error") + require.NotNil(t, got, "expected non-nil epoch") want := time.Unix(0, 0).UTC() - if !got.Equal(want) { - t.Fatalf("expected %v, got %v", want, *got) - } + require.Truef(t, got.Equal(want), "expected %v, got %v", want, *got) } diff --git a/pkg/downloader/chart_downloader_windows_test.go b/pkg/downloader/chart_downloader_windows_test.go index 732416701..2b6bea5eb 100644 --- a/pkg/downloader/chart_downloader_windows_test.go +++ b/pkg/downloader/chart_downloader_windows_test.go @@ -23,6 +23,9 @@ import ( "sync" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/getter" "helm.sh/helm/v4/pkg/repo/v1/repotest" @@ -36,9 +39,7 @@ func TestParallelDownloadTo(t *testing.T) { srv := repotest.NewTempServer(t, repotest.WithChartSourceGlob("testdata/*.tgz")) defer srv.Stop() - if err := srv.CreateIndex(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.CreateIndex()) dest := t.TempDir() cacheDir := t.TempDir() @@ -86,46 +87,38 @@ func TestParallelDownloadTo(t *testing.T) { } // With the file locking fix, all parallel downloads should succeed - if failedCount > 0 { - t.Errorf("Parallel downloads failed: %d out of %d downloads failed due to concurrent file access", failedCount, numDownloads) - } + assert.Falsef(t, failedCount > 0, "Parallel downloads failed: %d out of %d downloads failed due to concurrent file access", failedCount, numDownloads) // Verify the file exists and is valid expectedFile := filepath.Join(dest, "local-subchart-0.1.0.tgz") info, err := os.Stat(expectedFile) - if err != nil { - t.Errorf("Expected file %s does not exist: %v", expectedFile, err) - } else { - // Verify the file is not empty - if info.Size() == 0 { - t.Errorf("Downloaded file %s is empty (0 bytes)", expectedFile) - } - - // Verify the file has the expected size (should match the source file) - sourceFile := "testdata/local-subchart-0.1.0.tgz" - sourceInfo, err := os.Stat(sourceFile) - if err == nil && info.Size() != sourceInfo.Size() { - t.Errorf("Downloaded file size (%d bytes) doesn't match source file size (%d bytes)", - info.Size(), sourceInfo.Size()) - } + require.NoError(t, err, "Expected file %s does not exist: %v", expectedFile) + // Verify the file is not empty + assert.Falsef(t, info.Size() == 0, "Downloaded file %s is empty (0 bytes)", expectedFile) + + // Verify the file has the expected size (should match the source file) + sourceFile := "testdata/local-subchart-0.1.0.tgz" + sourceInfo, err := os.Stat(sourceFile) + if err == nil && info.Size() != sourceInfo.Size() { + t.Errorf("Downloaded file size (%d bytes) doesn't match source file size (%d bytes)", + info.Size(), sourceInfo.Size()) + } - // Verify it's a valid tar.gz file by checking the magic bytes - file, err := os.Open(expectedFile) - if err == nil { - defer file.Close() - // gzip magic bytes are 0x1f 0x8b - magic := make([]byte, 2) - if n, err := file.Read(magic); err == nil && n == 2 { - if magic[0] != 0x1f || magic[1] != 0x8b { - t.Errorf("Downloaded file is not a valid gzip file (magic bytes: %x)", magic) - } + // Verify it's a valid tar.gz file by checking the magic bytes + file, err := os.Open(expectedFile) + if err == nil { + defer file.Close() + // gzip magic bytes are 0x1f 0x8b + magic := make([]byte, 2) + if n, err := file.Read(magic); err == nil && n == 2 { + if magic[0] != 0x1f || magic[1] != 0x8b { + t.Errorf("Downloaded file is not a valid gzip file (magic bytes: %x)", magic) } } - - // Verify no lock file was left behind - lockFile := expectedFile + ".lock" - if _, err := os.Stat(lockFile); err == nil { - t.Errorf("Lock file %s was not cleaned up", lockFile) - } } + + // Verify no lock file was left behind + lockFile := expectedFile + ".lock" + _, err = os.Stat(lockFile) + assert.Falsef(t, err == nil, "Lock file %s was not cleaned up", lockFile) } diff --git a/pkg/kube/wait_test.go b/pkg/kube/wait_test.go index acdfffa23..9cf1e5506 100644 --- a/pkg/kube/wait_test.go +++ b/pkg/kube/wait_test.go @@ -19,7 +19,6 @@ package kube import ( "errors" "net/http" - "strings" "testing" "github.com/stretchr/testify/assert" @@ -298,17 +297,12 @@ func TestLegacyWaiter_waitForPodSuccess(t *testing.T) { t.Run(tt.name, func(t *testing.T) { done, err := lw.waitForPodSuccess(tt.obj, "foo") if tt.wantErr { - if err == nil { - t.Error("expected error, got none") - } else if !strings.Contains(err.Error(), tt.errMessage) { - t.Errorf("expected error to contain %q, got %q", tt.errMessage, err.Error()) - } - } else if err != nil { - t.Errorf("unexpected error: %v", err) - } - if done != tt.wantDone { - t.Errorf("got done=%v, want %v", done, tt.wantDone) + require.Error(t, err, "expected error, got none") + require.ErrorContains(t, err, tt.errMessage, "expected error to contain %q, got %q", tt.errMessage, err.Error()) + } else { + require.NoError(t, err) } + assert.Equal(t, tt.wantDone, done, "got done=%v, want %v", done, tt.wantDone) }) } } @@ -390,17 +384,11 @@ func TestLegacyWaiter_waitForJob(t *testing.T) { t.Run(tt.name, func(t *testing.T) { done, err := lw.waitForJob(tt.obj, "test-job") if tt.wantErr { - if err == nil { - t.Error("expected error, got none") - } else if !strings.Contains(err.Error(), tt.errMessage) { - t.Errorf("expected error to contain %q, got %q", tt.errMessage, err.Error()) - } - } else if err != nil { - t.Errorf("unexpected error: %v", err) - } - - if done != tt.wantDone { - t.Errorf("got done=%v, want %v", done, tt.wantDone) + require.Error(t, err, "expected error, got none") + require.ErrorContainsf(t, err, tt.errMessage, "expected error to contain %q, got %q", tt.errMessage, err.Error()) + } else { + require.NoError(t, err) + assert.Equal(t, tt.wantDone, done, "got done=%v, want %v", done, tt.wantDone) } }) } @@ -459,9 +447,7 @@ func TestLegacyWaiter_isRetryableError(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := lw.isRetryableError(tt.err, info) - if got != tt.wantRetry { - t.Errorf("isRetryableError() = %v, want %v", got, tt.wantRetry) - } + assert.Equal(t, tt.wantRetry, got, "isRetryableError() = %v, want %v", got, tt.wantRetry) }) } } diff --git a/pkg/registry/client_tls_test.go b/pkg/registry/client_tls_test.go index 45eff232c..0bad5df64 100644 --- a/pkg/registry/client_tls_test.go +++ b/pkg/registry/client_tls_test.go @@ -40,22 +40,19 @@ func (suite *TLSRegistryClientTestSuite) TearDownSuite() { } func (suite *TLSRegistryClientTestSuite) Test_0_Login() { - err := suite.RegistryClient.Login(suite.DockerRegistryHost, + suite.Require().Error(suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth("badverybad", "ohsobad"), - LoginOptTLSClientConfig(tlsCert, tlsKey, tlsCA)) - suite.Require().Error(err, "error logging into registry with bad credentials") + LoginOptTLSClientConfig(tlsCert, tlsKey, tlsCA)), "error logging into registry with bad credentials") - err = suite.RegistryClient.Login(suite.DockerRegistryHost, + suite.Require().NoError(suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth(testUsername, testPassword), - LoginOptTLSClientConfig(tlsCert, tlsKey, tlsCA)) - suite.Require().NoError(err, "no error logging into registry with good credentials") + LoginOptTLSClientConfig(tlsCert, tlsKey, tlsCA)), "no error logging into registry with good credentials") } func (suite *TLSRegistryClientTestSuite) Test_1_Login() { - err := suite.RegistryClient.Login(suite.DockerRegistryHost, + suite.Require().Error(suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth("badverybad", "ohsobad"), - LoginOptTLSClientConfigFromConfig(&tls.Config{})) - suite.Require().Error(err, "error logging into registry with bad credentials") + LoginOptTLSClientConfigFromConfig(&tls.Config{})), "error logging into registry with bad credentials") // Create a *tls.Config from tlsCert, tlsKey, and tlsCA. cert, err := tls.LoadX509KeyPair(tlsCert, tlsKey) @@ -69,10 +66,9 @@ func (suite *TLSRegistryClientTestSuite) Test_1_Login() { RootCAs: rootCAs, } - err = suite.RegistryClient.Login(suite.DockerRegistryHost, + suite.Require().NoError(suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth(testUsername, testPassword), - LoginOptTLSClientConfigFromConfig(conf)) - suite.Require().NoError(err, "no error logging into registry with good credentials") + LoginOptTLSClientConfigFromConfig(conf)), "no error logging into registry with good credentials") } func (suite *TLSRegistryClientTestSuite) Test_1_Push() { @@ -94,8 +90,7 @@ func (suite *TLSRegistryClientTestSuite) Test_4_Logout() { suite.Require().Error(err, "failed to delete the credential for this-host-aint-real:5000") } - err = suite.RegistryClient.Logout(suite.DockerRegistryHost) - suite.Require().NoError(err, "no error logging out of registry") + suite.Require().NoError(suite.RegistryClient.Logout(suite.DockerRegistryHost), "no error logging out of registry") } func TestTLSRegistryClientTestSuite(t *testing.T) { diff --git a/pkg/release/v1/info_test.go b/pkg/release/v1/info_test.go index 12b3014f6..61a888cb8 100644 --- a/pkg/release/v1/info_test.go +++ b/pkg/release/v1/info_test.go @@ -253,17 +253,17 @@ func TestInfoUnmarshalJSON(t *testing.T) { err := json.Unmarshal([]byte(tt.input), &info) if tt.wantErr { assert.Error(t, err) - return + } else { + require.NoError(t, err) + assert.Equal(t, tt.expected.FirstDeployed.Unix(), info.FirstDeployed.Unix()) + assert.Equal(t, tt.expected.LastDeployed.Unix(), info.LastDeployed.Unix()) + assert.Equal(t, tt.expected.Deleted.Unix(), info.Deleted.Unix()) + assert.Equal(t, tt.expected.Description, info.Description) + assert.Equal(t, tt.expected.Status, info.Status) + assert.Equal(t, tt.expected.RollbackRevision, info.RollbackRevision) + assert.Equal(t, tt.expected.Notes, info.Notes) + assert.Equal(t, tt.expected.Resources, info.Resources) } - require.NoError(t, err) - assert.Equal(t, tt.expected.FirstDeployed.Unix(), info.FirstDeployed.Unix()) - assert.Equal(t, tt.expected.LastDeployed.Unix(), info.LastDeployed.Unix()) - assert.Equal(t, tt.expected.Deleted.Unix(), info.Deleted.Unix()) - assert.Equal(t, tt.expected.Description, info.Description) - assert.Equal(t, tt.expected.Status, info.Status) - assert.Equal(t, tt.expected.RollbackRevision, info.RollbackRevision) - assert.Equal(t, tt.expected.Notes, info.Notes) - assert.Equal(t, tt.expected.Resources, info.Resources) }) } } @@ -284,9 +284,7 @@ func TestInfoRoundTrip(t *testing.T) { require.NoError(t, err) var decoded Info - err = json.Unmarshal(data, &decoded) - require.NoError(t, err) - + require.NoError(t, json.Unmarshal(data, &decoded)) assert.Equal(t, original.FirstDeployed.Unix(), decoded.FirstDeployed.Unix()) assert.Equal(t, original.LastDeployed.Unix(), decoded.LastDeployed.Unix()) assert.Equal(t, original.Deleted.Unix(), decoded.Deleted.Unix()) @@ -330,9 +328,7 @@ func TestInfoRollbackRevisionRoundTrip(t *testing.T) { require.NoError(t, err) var decoded Info - err = json.Unmarshal(data, &decoded) - require.NoError(t, err) - + require.NoError(t, json.Unmarshal(data, &decoded)) assert.Equal(t, tt.info.RollbackRevision, decoded.RollbackRevision) assert.Equal(t, tt.info.FirstDeployed.Unix(), decoded.FirstDeployed.Unix()) assert.Equal(t, tt.info.LastDeployed.Unix(), decoded.LastDeployed.Unix()) @@ -342,8 +338,7 @@ func TestInfoRollbackRevisionRoundTrip(t *testing.T) { // Verify omitempty behavior: zero rollback_revision should not appear in JSON if tt.info.RollbackRevision == 0 { var raw map[string]any - err = json.Unmarshal(data, &raw) - require.NoError(t, err) + require.NoError(t, json.Unmarshal(data, &raw)) assert.NotContains(t, raw, "rollback_revision") } }) @@ -356,13 +351,12 @@ func TestInfoEmptyStringRoundTrip(t *testing.T) { input := `{"first_deployed":"","last_deployed":"","deleted":"","status":"deployed","description":"test"}` var info Info - err := json.Unmarshal([]byte(input), &info) - require.NoError(t, err) + require.NoError(t, json.Unmarshal([]byte(input), &info)) // Verify time fields are zero values - assert.True(t, info.FirstDeployed.IsZero()) - assert.True(t, info.LastDeployed.IsZero()) - assert.True(t, info.Deleted.IsZero()) + assert.Zero(t, info.FirstDeployed) + assert.Zero(t, info.LastDeployed) + assert.Zero(t, info.Deleted) assert.Equal(t, common.StatusDeployed, info.Status) assert.Equal(t, "test", info.Description) @@ -371,8 +365,7 @@ func TestInfoEmptyStringRoundTrip(t *testing.T) { require.NoError(t, err) var result map[string]any - err = json.Unmarshal(data, &result) - require.NoError(t, err) + require.NoError(t, json.Unmarshal(data, &result)) // Zero time values should be omitted due to omitzero tag assert.NotContains(t, result, "first_deployed") From a934b827fa9569c8769c981b1d4847f72a4c1f94 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:57 +0200 Subject: [PATCH 476/571] chore(pkg): refactor: convert tests to testify assert/require part 21 #### Description refactor: convert tests to testify assert/require in pkg/cmd Signed-off-by: Matthieu MOREL --- pkg/action/action_test.go | 20 ++++---------- pkg/action/dependency_test.go | 24 +++++------------ pkg/action/list_test.go | 29 +++++++------------- pkg/chart/v2/lint/support/message_test.go | 29 +++++++------------- pkg/cmd/history_test.go | 30 +++++++++------------ pkg/cmd/require/args_test.go | 16 ++++-------- pkg/cmd/rollback_test.go | 26 +++++++----------- pkg/cmd/root_test.go | 32 ++++++++--------------- pkg/cmd/search_hub_test.go | 28 +++++++------------- pkg/engine/files_test.go | 14 ++++------ pkg/engine/funcs_test.go | 12 +++------ pkg/getter/plugingetter_test.go | 23 ++++++---------- pkg/pusher/pusher_test.go | 31 +++++++++------------- pkg/registry/client_insecure_tls_test.go | 13 ++++----- pkg/release/v1/hook_test.go | 24 +++++++---------- pkg/storage/driver/mock_test.go | 18 ++++--------- pkg/storage/driver/util_test.go | 22 +++++++--------- 17 files changed, 138 insertions(+), 253 deletions(-) diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index 880a9930b..056c539a5 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -58,9 +58,7 @@ func actionConfigFixtureWithDummyResources(t *testing.T, dummyResources kube.Res slog.SetDefault(logger) registryClient, err := registry.NewClient() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return &Configuration{ Releases: storage.Init(driver.NewMemory()), @@ -371,16 +369,10 @@ func TestGetVersionSet(t *testing.T) { client := fakeclientset.NewClientset() vs, err := GetVersionSet(client.Discovery()) - if err != nil { - t.Error(err) - } + require.NoError(t, err) - if !vs.Has("v1") { - t.Error("Expected supported versions to at least include v1.") - } - if vs.Has("nosuchversion/v1") { - t.Error("Non-existent version is reported found.") - } + assert.True(t, vs.Has("v1"), "Expected supported versions to at least include v1.") + assert.False(t, vs.Has("nosuchversion/v1"), "Non-existent version is reported found.") } // Mock PostRenderer for testing @@ -2031,9 +2023,7 @@ spec: mockPR := &mockPostRenderer{ transform: func(content string) string { count := strings.Count(content, "kind: ServiceAccount") - if count > 1 { - t.Errorf("post-renderer received %d ServiceAccount resources in a single stream, expected at most 1", count) - } + assert.LessOrEqualf(t, count, 1, "post-renderer received %d ServiceAccount resources in a single stream, expected at most 1", count) return content }, } diff --git a/pkg/action/dependency_test.go b/pkg/action/dependency_test.go index 25664b98c..adbe311d6 100644 --- a/pkg/action/dependency_test.go +++ b/pkg/action/dependency_test.go @@ -57,9 +57,7 @@ func TestList(t *testing.T) { }, } { buf := bytes.Buffer{} - if err := NewDependency().List(tcase.chart, &buf); err != nil { - t.Fatal(err) - } + require.NoError(t, NewDependency().List(tcase.chart, &buf)) test.AssertGoldenString(t, buf.String(), tcase.golden) } } @@ -71,22 +69,16 @@ func TestDependencyStatus_Dashes(t *testing.T) { dir := t.TempDir() chartpath := filepath.Join(dir, "charts") - if err := os.MkdirAll(chartpath, 0o700); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(chartpath, 0o700)) // Add some fake charts first := buildChart(withName("first-chart")) _, err := chartutil.Save(first, chartpath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) second := buildChart(withName("first-chart-second-chart")) _, err = chartutil.Save(second, chartpath) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) dep := &chart.Dependency{ Name: "first-chart", @@ -95,9 +87,7 @@ func TestDependencyStatus_Dashes(t *testing.T) { // Now try to get the deps stat := NewDependency().dependencyStatus(dir, dep, first) - if stat != "ok" { - t.Errorf("Unexpected status: %q", stat) - } + assert.Equal(t, "ok", stat, "Unexpected status: %q", stat) } func TestStatArchiveForStatus(t *testing.T) { @@ -105,9 +95,7 @@ func TestStatArchiveForStatus(t *testing.T) { dir := t.TempDir() chartpath := filepath.Join(dir, "charts") - if err := os.MkdirAll(chartpath, 0o700); err != nil { - t.Fatal(err) - } + require.NoError(t, os.MkdirAll(chartpath, 0o700)) // unsaved chart lilith := buildChart(withName("lilith")) diff --git a/pkg/action/list_test.go b/pkg/action/list_test.go index e7c2770d7..1c849028d 100644 --- a/pkg/action/list_test.go +++ b/pkg/action/list_test.go @@ -44,9 +44,7 @@ func TestListStates(t *testing.T) { "unknown": ListUnknown, "totally made up key": ListUnknown, } { - if expect != expect.FromName(input) { - t.Errorf("Expected %d for %s", expect, input) - } + assert.Equal(t, expect.FromName(input), expect, "Expected %d for %s", expect, input) // This is a cheap way to verify that ListAll actually allows everything but Unknown if got := expect.FromName(input); got != ListUnknown && got&ListAll == 0 { t.Errorf("Expected %s to match the ListAll filter", input) @@ -54,12 +52,10 @@ func TestListStates(t *testing.T) { } filter := ListDeployed | ListPendingRollback - if status := filter.FromName("deployed"); filter&status == 0 { - t.Errorf("Expected %d to match mask %d", status, filter) - } - if status := filter.FromName("failed"); filter&status != 0 { - t.Errorf("Expected %d to fail to match mask %d", status, filter) - } + status := filter.FromName("deployed") + assert.NotEqualf(t, ListStates(0), filter&status, "Expected %d to match mask %d", status, filter) + status = filter.FromName("failed") + assert.Equalf(t, ListStates(0), filter&status, "Expected %d to fail to match mask %d", status, filter) } func TestList_Empty(t *testing.T) { @@ -202,8 +198,7 @@ func TestList_StateMask(t *testing.T) { } one.SetStatus(common.StatusUninstalled, "uninstalled") - err = lister.cfg.Releases.Update(one) - req.NoError(err) + req.NoError(lister.cfg.Releases.Update(one)) res, err := lister.Run() req.NoError(err) @@ -277,9 +272,7 @@ func makeMeSomeReleasesWithStaleFailure(t *testing.T, store *storage.Storage) { five.Version = 1 for _, rel := range []*release.Release{one, two, three, four, five} { - if err := store.Create(rel); err != nil { - t.Fatal(err) - } + require.NoError(t, store.Create(rel)) } all, err := store.ListReleases() @@ -328,9 +321,7 @@ func makeMeSomeReleases(t *testing.T, store *storage.Storage) { three.Version = 3 for _, rel := range []*release.Release{one, two, three} { - if err := store.Create(rel); err != nil { - t.Fatal(err) - } + require.NoError(t, store.Create(rel)) } all, err := store.ListReleases() @@ -389,9 +380,7 @@ func TestSelectorList(t *testing.T) { lister := newListFixture(t) for _, rel := range []*release.Release{r1, r2, r3} { - if err := lister.cfg.Releases.Create(rel); err != nil { - t.Fatal(err) - } + require.NoError(t, lister.cfg.Releases.Create(rel)) } t.Run("should fail selector parsing", func(t *testing.T) { diff --git a/pkg/chart/v2/lint/support/message_test.go b/pkg/chart/v2/lint/support/message_test.go index ce5b5e42e..dfbc65844 100644 --- a/pkg/chart/v2/lint/support/message_test.go +++ b/pkg/chart/v2/lint/support/message_test.go @@ -19,6 +19,9 @@ package support import ( "errors" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var errLint = errors.New("lint failed") @@ -47,33 +50,19 @@ func TestRunLinterRule(t *testing.T) { linter := Linter{} for _, test := range tests { isValid := linter.RunLinterRule(test.Severity, "chart", test.LintError) - if len(linter.Messages) != test.ExpectedMessages { - t.Errorf("RunLinterRule(%d, \"chart\", %v), linter.Messages should now have %d message, we got %d", test.Severity, test.LintError, test.ExpectedMessages, len(linter.Messages)) - } - - if linter.HighestSeverity != test.ExpectedHighestSeverity { - t.Errorf("RunLinterRule(%d, \"chart\", %v), linter.HighestSeverity should be %d, we got %d", test.Severity, test.LintError, test.ExpectedHighestSeverity, linter.HighestSeverity) - } - - if isValid != test.ExpectedReturn { - t.Errorf("RunLinterRule(%d, \"chart\", %v), should have returned %t but returned %t", test.Severity, test.LintError, test.ExpectedReturn, isValid) - } + assert.Lenf(t, linter.Messages, test.ExpectedMessages, "RunLinterRule(%d, \"chart\", %v), linter.Messages should now have %d message, we got %d", test.Severity, test.LintError, test.ExpectedMessages, len(linter.Messages)) + assert.Equalf(t, linter.HighestSeverity, test.ExpectedHighestSeverity, "RunLinterRule(%d, \"chart\", %v), linter.HighestSeverity should be %d, we got %d", test.Severity, test.LintError, test.ExpectedHighestSeverity, linter.HighestSeverity) + assert.Equalf(t, isValid, test.ExpectedReturn, "RunLinterRule(%d, \"chart\", %v), should have returned %t but returned %t", test.Severity, test.LintError, test.ExpectedReturn, isValid) } } func TestMessage(t *testing.T) { m := Message{ErrorSev, "Chart.yaml", errors.New("Foo")} - if m.Error() != "[ERROR] Chart.yaml: Foo" { - t.Errorf("Unexpected output: %s", m.Error()) - } + require.EqualErrorf(t, m, "[ERROR] Chart.yaml: Foo", "Unexpected output: %s", m.Error()) m = Message{WarningSev, "templates/", errors.New("Bar")} - if m.Error() != "[WARNING] templates/: Bar" { - t.Errorf("Unexpected output: %s", m.Error()) - } + require.EqualErrorf(t, m, "[WARNING] templates/: Bar", "Unexpected output: %s", m.Error()) m = Message{InfoSev, "templates/rc.yaml", errors.New("FooBar")} - if m.Error() != "[INFO] templates/rc.yaml: FooBar" { - t.Errorf("Unexpected output: %s", m.Error()) - } + assert.EqualErrorf(t, m, "[INFO] templates/rc.yaml: FooBar", "Unexpected output: %s", m.Error()) } diff --git a/pkg/cmd/history_test.go b/pkg/cmd/history_test.go index 2bace98e0..cd8e353e7 100644 --- a/pkg/cmd/history_test.go +++ b/pkg/cmd/history_test.go @@ -385,16 +385,16 @@ func TestReleaseInfoUnmarshalJSON(t *testing.T) { err := json.Unmarshal([]byte(tt.input), &info) if tt.wantErr { assert.Error(t, err) - return + } else { + require.NoError(t, err) + assert.Equal(t, tt.expected.Revision, info.Revision) + assert.Equal(t, tt.expected.Updated.Unix(), info.Updated.Unix()) + assert.Equal(t, tt.expected.Status, info.Status) + assert.Equal(t, tt.expected.Chart, info.Chart) + assert.Equal(t, tt.expected.AppVersion, info.AppVersion) + assert.Equal(t, tt.expected.RollbackRevision, info.RollbackRevision) + assert.Equal(t, tt.expected.Description, info.Description) } - require.NoError(t, err) - assert.Equal(t, tt.expected.Revision, info.Revision) - assert.Equal(t, tt.expected.Updated.Unix(), info.Updated.Unix()) - assert.Equal(t, tt.expected.Status, info.Status) - assert.Equal(t, tt.expected.Chart, info.Chart) - assert.Equal(t, tt.expected.AppVersion, info.AppVersion) - assert.Equal(t, tt.expected.RollbackRevision, info.RollbackRevision) - assert.Equal(t, tt.expected.Description, info.Description) }) } } @@ -415,9 +415,7 @@ func TestReleaseInfoRoundTrip(t *testing.T) { require.NoError(t, err) var decoded releaseInfo - err = json.Unmarshal(data, &decoded) - require.NoError(t, err) - + require.NoError(t, json.Unmarshal(data, &decoded)) assert.Equal(t, original.Revision, decoded.Revision) assert.Equal(t, original.Updated.Unix(), decoded.Updated.Unix()) assert.Equal(t, original.Status, decoded.Status) @@ -433,11 +431,10 @@ func TestReleaseInfoEmptyStringRoundTrip(t *testing.T) { input := `{"revision":1,"updated":"","status":"deployed","chart":"mychart-1.0.0","app_version":"1.0.0","description":"Test"}` var info releaseInfo - err := json.Unmarshal([]byte(input), &info) - require.NoError(t, err) + require.NoError(t, json.Unmarshal([]byte(input), &info)) // Verify time field is zero value - assert.True(t, info.Updated.IsZero()) + assert.Zero(t, info.Updated) assert.Equal(t, 1, info.Revision) assert.Equal(t, "deployed", info.Status) @@ -446,8 +443,7 @@ func TestReleaseInfoEmptyStringRoundTrip(t *testing.T) { require.NoError(t, err) var result map[string]any - err = json.Unmarshal(data, &result) - require.NoError(t, err) + require.NoError(t, json.Unmarshal(data, &result)) // Zero time value should be omitted assert.NotContains(t, result, "updated") diff --git a/pkg/cmd/require/args_test.go b/pkg/cmd/require/args_test.go index 89403140e..5d3a9f003 100644 --- a/pkg/cmd/require/args_test.go +++ b/pkg/cmd/require/args_test.go @@ -18,10 +18,10 @@ package require import ( "io" "strconv" - "strings" "testing" "github.com/spf13/cobra" + "github.com/stretchr/testify/require" ) func TestArgs(t *testing.T) { @@ -77,16 +77,10 @@ func runTestCases(t *testing.T, testCases []testCase) { err := cmd.Execute() if tc.wantError == "" { - if err != nil { - t.Fatalf("unexpected error, got '%v'", err) - } - return - } - if !strings.Contains(err.Error(), tc.wantError) { - t.Fatalf("unexpected error \n\nWANT:\n%q\n\nGOT:\n%q\n", tc.wantError, err) - } - if !strings.Contains(err.Error(), "Usage:") { - t.Fatalf("unexpected error: want Usage string\n\nGOT:\n%q\n", err) + require.NoError(t, err) + } else { + require.ErrorContains(t, err, tc.wantError) + require.ErrorContains(t, err, "Usage:") } }) } diff --git a/pkg/cmd/rollback_test.go b/pkg/cmd/rollback_test.go index 116e158fd..ff7f314c9 100644 --- a/pkg/cmd/rollback_test.go +++ b/pkg/cmd/rollback_test.go @@ -21,6 +21,9 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/release/common" release "helm.sh/helm/v4/pkg/release/v1" @@ -148,24 +151,15 @@ func TestRollbackWithLabels(t *testing.T) { } storage := storageFixture() for _, rel := range rels { - if err := storage.Create(rel); err != nil { - t.Fatal(err) - } + require.NoError(t, storage.Create(rel)) } _, _, err := executeActionCommandC(storage, fmt.Sprintf("rollback %s 1", releaseName)) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) + updatedReli, err := storage.Get(releaseName, 3) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } - updatedRel, err := releaserToV1Release(updatedReli) - if err != nil { - t.Errorf("unexpected error, got '%v'", err) - } + require.NoError(t, err) - if !reflect.DeepEqual(updatedRel.Labels, labels1) { - t.Errorf("Expected {%v}, got {%v}", labels1, updatedRel.Labels) - } + updatedRel, err := releaserToV1Release(updatedReli) + require.NoError(t, err) + assert.Truef(t, reflect.DeepEqual(updatedRel.Labels, labels1), "Expected {%v}, got {%v}", labels1, updatedRel.Labels) } diff --git a/pkg/cmd/root_test.go b/pkg/cmd/root_test.go index 316e6bd2e..b07bdbbf6 100644 --- a/pkg/cmd/root_test.go +++ b/pkg/cmd/root_test.go @@ -23,6 +23,9 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/internal/test/ensure" "helm.sh/helm/v4/pkg/action" "helm.sh/helm/v4/pkg/helmpath" @@ -86,9 +89,8 @@ func TestRootCmd(t *testing.T) { t.Setenv(k, v) } - if _, _, err := executeActionCommand(tt.args); err != nil { - t.Fatalf("unexpected error: %s", err) - } + _, _, err := executeActionCommand(tt.args) + require.NoError(t, err) // NOTE(bacongobbler): we need to check here after calling ensure.HelmHome so we // load the proper paths after XDG_*_HOME is set @@ -104,15 +106,9 @@ func TestRootCmd(t *testing.T) { tt.dataPath = filepath.Join(os.Getenv(xdg.DataHomeEnvVar), "helm") } - if helmpath.CachePath() != tt.cachePath { - t.Errorf("expected cache path %q, got %q", tt.cachePath, helmpath.CachePath()) - } - if helmpath.ConfigPath() != tt.configPath { - t.Errorf("expected config path %q, got %q", tt.configPath, helmpath.ConfigPath()) - } - if helmpath.DataPath() != tt.dataPath { - t.Errorf("expected data path %q, got %q", tt.dataPath, helmpath.DataPath()) - } + assert.Equal(t, tt.cachePath, helmpath.CachePath(), "expected cache path %q, got %q", tt.cachePath, helmpath.CachePath()) + assert.Equal(t, tt.configPath, helmpath.ConfigPath(), "expected config path %q, got %q", tt.configPath, helmpath.ConfigPath()) + assert.Equal(t, tt.dataPath, helmpath.DataPath(), "expected data path %q, got %q", tt.dataPath, helmpath.DataPath()) }) } } @@ -120,9 +116,7 @@ func TestRootCmd(t *testing.T) { func TestUnknownSubCmd(t *testing.T) { _, _, err := executeActionCommand("foobar") - if err == nil || err.Error() != `unknown command "foobar" for "helm"` { - t.Errorf("Expect unknown command error, got %q", err) - } + assert.EqualErrorf(t, err, `unknown command "foobar" for "helm"`, "Expect unknown command error") } // Need the release of Cobra following 1.0 to be able to disable @@ -138,14 +132,10 @@ func TestRootCmdLogger(t *testing.T) { buf := new(bytes.Buffer) actionConfig := action.NewConfiguration() _, err := newRootCmdWithConfig(actionConfig, buf, args, SetupLogging) - if err != nil { - t.Errorf("expected no error, got: '%v'", err) - } + require.NoError(t, err) l1 := actionConfig.Logger() l2 := slog.Default() - if l1.Handler() != l2.Handler() { - t.Error("expected actionConfig logger to be the slog default logger") - } + assert.Equal(t, l2.Handler(), l1.Handler(), "expected actionConfig logger to be the slog default logger") } diff --git a/pkg/cmd/search_hub_test.go b/pkg/cmd/search_hub_test.go index 98bf052f2..7b00cc295 100644 --- a/pkg/cmd/search_hub_test.go +++ b/pkg/cmd/search_hub_test.go @@ -21,6 +21,9 @@ import ( "net/http" "net/http/httptest" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestSearchHubCmd(t *testing.T) { @@ -42,9 +45,7 @@ func TestSearchHubCmd(t *testing.T) { testcmd := "search hub --endpoint " + ts.URL + " maria" storage := storageFixture() _, out, err := executeActionCommandC(storage, testcmd) - if err != nil { - t.Errorf("unexpected error, %s", err) - } + require.NoError(t, err) if out != expected { t.Error("expected and actual output did not match") t.Log(out) @@ -71,9 +72,7 @@ func TestSearchHubListRepoCmd(t *testing.T) { testcmd := "search hub --list-repo-url --endpoint " + ts.URL + " maria" storage := storageFixture() _, out, err := executeActionCommandC(storage, testcmd) - if err != nil { - t.Errorf("unexpected error, %s", err) - } + require.NoError(t, err) if out != expected { t.Error("expected and actual output did not match") t.Log(out) @@ -165,21 +164,14 @@ func TestSearchHubCmd_FailOnNoResponseTests(t *testing.T) { _, out, err := executeActionCommandC(storage, tt.cmd) if tt.wantErr { - if err == nil { - t.Error("expected error due to no record in response, got nil") - } + require.Error(t, err, "expected error due to no record in response, got nil") } else { - if err != nil { - t.Errorf("unexpected error, got %q", err) - } + require.NoError(t, err) } - if out != tt.expected { - t.Errorf("expected and actual output did not match\n"+ - "expected: %q\n"+ - "actual : %q", - tt.expected, out) - } + assert.Equal(t, tt.expected, out, "expected and actual output did not match\n"+ + "expected: %q\n"+ + "actual : %q", tt.expected, out) }) } } diff --git a/pkg/engine/files_test.go b/pkg/engine/files_test.go index b4318a597..6ff251aad 100644 --- a/pkg/engine/files_test.go +++ b/pkg/engine/files_test.go @@ -44,17 +44,13 @@ func getTestFiles() files { func TestNewFiles(t *testing.T) { files := getTestFiles() - if len(files) != len(cases) { - t.Errorf("Expected len() = %d, got %d", len(cases), len(files)) - } + assert.Len(t, files, len(cases), "Expected len() = %d, got %d", len(cases), len(files)) for i, f := range cases { - if got := string(files.GetBytes(f.path)); got != f.data { - t.Errorf("%d: expected %q, got %q", i, f.data, got) - } - if got := files.Get(f.path); got != f.data { - t.Errorf("%d: expected %q, got %q", i, f.data, got) - } + got := string(files.GetBytes(f.path)) + assert.Equalf(t, got, f.data, "%d: expected %q, got %q", i, f.data, got) + got = files.Get(f.path) + assert.Equalf(t, got, f.data, "%d: expected %q, got %q", i, f.data, got) } } diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go index bc541339b..beb87b429 100644 --- a/pkg/engine/funcs_test.go +++ b/pkg/engine/funcs_test.go @@ -134,8 +134,7 @@ keyInElement1 = "valueInElement1"`, for _, tt := range tests { var b strings.Builder - err := template.Must(template.New("test").Funcs(funcMap()).Parse(tt.tpl)).Execute(&b, tt.vars) - require.NoError(t, err) + require.NoError(t, template.Must(template.New("test").Funcs(funcMap()).Parse(tt.tpl)).Execute(&b, tt.vars)) assert.Equal(t, tt.expect, b.String(), tt.tpl) } @@ -360,8 +359,7 @@ func TestDurationHelpers(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var b strings.Builder - err := template.Must(template.New("test").Funcs(funcMap()).Parse(tt.tpl)).Execute(&b, tt.vars) - require.NoError(t, err, tt.tpl) + require.NoError(t, template.Must(template.New("test").Funcs(funcMap()).Parse(tt.tpl)).Execute(&b, tt.vars), tt.tpl) assert.Equal(t, tt.expect, b.String(), tt.tpl) }) } @@ -422,8 +420,7 @@ func TestDurationHelpers(t *testing.T) { Funcs(funcMap()). Parse(tt.tpl), ) - err := tmpl.Execute(&b, tt.vars) - require.Error(t, err, tt.tpl) + require.Error(t, tmpl.Execute(&b, tt.vars), tt.tpl) }) } } @@ -474,8 +471,7 @@ func TestMerge(t *testing.T) { } tpl := `{{merge .dst .src1 .src2}}` var b strings.Builder - err := template.Must(template.New("test").Funcs(funcMap()).Parse(tpl)).Execute(&b, dict) - require.NoError(t, err) + require.NoError(t, template.Must(template.New("test").Funcs(funcMap()).Parse(tpl)).Execute(&b, dict)) expected := map[string]any{ "a": "one", // key overridden diff --git a/pkg/getter/plugingetter_test.go b/pkg/getter/plugingetter_test.go index 16af9eb31..c9ed9f779 100644 --- a/pkg/getter/plugingetter_test.go +++ b/pkg/getter/plugingetter_test.go @@ -35,25 +35,18 @@ func TestCollectPlugins(t *testing.T) { env.PluginsDirectory = pluginDir p, err := collectGetterPlugins(env) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if len(p) != 2 { - t.Errorf("Expected 2 plugins, got %d: %v", len(p), p) - } + assert.Len(t, p, 2, "Expected 2 plugins, got %d: %v", len(p), p) - if _, err := p.ByScheme("test2"); err != nil { - t.Error(err) - } + _, err = p.ByScheme("test2") + require.NoError(t, err) - if _, err := p.ByScheme("test"); err != nil { - t.Error(err) - } + _, err = p.ByScheme("test") + require.NoError(t, err) - if _, err := p.ByScheme("nosuchthing"); err == nil { - t.Fatal("did not expect protocol handler for nosuchthing") - } + _, err = p.ByScheme("nosuchthing") + require.Error(t, err, "did not expect protocol handler for nosuchthing") } func TestConvertOptions(t *testing.T) { diff --git a/pkg/pusher/pusher_test.go b/pkg/pusher/pusher_test.go index 71fab8694..078b2fa73 100644 --- a/pkg/pusher/pusher_test.go +++ b/pkg/pusher/pusher_test.go @@ -18,6 +18,9 @@ package pusher import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/registry" ) @@ -28,9 +31,7 @@ func TestProvider(t *testing.T) { func(_ ...Option) (Pusher, error) { return nil, nil }, } - if !p.Provides("three") { - t.Error("Expected provider to provide three") - } + assert.True(t, p.Provides("three"), "Expected provider to provide three") } func TestProviders(t *testing.T) { @@ -39,30 +40,24 @@ func TestProviders(t *testing.T) { {[]string{"two", "four"}, func(_ ...Option) (Pusher, error) { return nil, nil }}, } - if _, err := ps.ByScheme("one"); err != nil { - t.Error(err) - } - if _, err := ps.ByScheme("four"); err != nil { - t.Error(err) - } + _, err := ps.ByScheme("one") + require.NoError(t, err) + _, err = ps.ByScheme("four") + require.NoError(t, err) - if _, err := ps.ByScheme("five"); err == nil { - t.Error("Did not expect handler for five") - } + _, err = ps.ByScheme("five") + assert.Error(t, err, "Did not expect handler for five") } func TestAll(t *testing.T) { env := cli.New() all := All(env) - if len(all) != 1 { - t.Errorf("expected 1 provider (OCI), got %d", len(all)) - } + assert.Len(t, all, 1, "expected 1 provider (OCI), got %d", len(all)) } func TestByScheme(t *testing.T) { env := cli.New() g := All(env) - if _, err := g.ByScheme(registry.OCIScheme); err != nil { - t.Error(err) - } + _, err := g.ByScheme(registry.OCIScheme) + assert.NoError(t, err) } diff --git a/pkg/registry/client_insecure_tls_test.go b/pkg/registry/client_insecure_tls_test.go index ae0a44337..f1ece1b1c 100644 --- a/pkg/registry/client_insecure_tls_test.go +++ b/pkg/registry/client_insecure_tls_test.go @@ -38,15 +38,13 @@ func (suite *InsecureTLSRegistryClientTestSuite) TearDownSuite() { } func (suite *InsecureTLSRegistryClientTestSuite) Test_0_Login() { - err := suite.RegistryClient.Login(suite.DockerRegistryHost, + suite.Require().Error(suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth("badverybad", "ohsobad"), - LoginOptInsecure(true)) - suite.Require().Error(err, "error logging into registry with bad credentials") + LoginOptInsecure(true)), "error logging into registry with bad credentials") - err = suite.RegistryClient.Login(suite.DockerRegistryHost, + suite.Require().NoError(suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth(testUsername, testPassword), - LoginOptInsecure(true)) - suite.Require().NoError(err, "no error logging into registry with good credentials") + LoginOptInsecure(true)), "no error logging into registry with good credentials") } func (suite *InsecureTLSRegistryClientTestSuite) Test_1_Push() { @@ -68,8 +66,7 @@ func (suite *InsecureTLSRegistryClientTestSuite) Test_4_Logout() { suite.Require().Error(err, "failed to delete the credential for this-host-aint-real:5000") } - err = suite.RegistryClient.Logout(suite.DockerRegistryHost) - suite.Require().NoError(err, "no error logging out of registry") + suite.Require().NoError(suite.RegistryClient.Logout(suite.DockerRegistryHost), "no error logging out of registry") } func TestInsecureTLSRegistryClientTestSuite(t *testing.T) { diff --git a/pkg/release/v1/hook_test.go b/pkg/release/v1/hook_test.go index f3b8811a6..9e803a0fc 100644 --- a/pkg/release/v1/hook_test.go +++ b/pkg/release/v1/hook_test.go @@ -170,12 +170,12 @@ func TestHookExecutionUnmarshalJSON(t *testing.T) { err := json.Unmarshal([]byte(tt.input), &exec) if tt.wantErr { assert.Error(t, err) - return + } else { + require.NoError(t, err) + assert.Equal(t, tt.expected.StartedAt.Unix(), exec.StartedAt.Unix()) + assert.Equal(t, tt.expected.CompletedAt.Unix(), exec.CompletedAt.Unix()) + assert.Equal(t, tt.expected.Phase, exec.Phase) } - require.NoError(t, err) - assert.Equal(t, tt.expected.StartedAt.Unix(), exec.StartedAt.Unix()) - assert.Equal(t, tt.expected.CompletedAt.Unix(), exec.CompletedAt.Unix()) - assert.Equal(t, tt.expected.Phase, exec.Phase) }) } } @@ -194,9 +194,7 @@ func TestHookExecutionRoundTrip(t *testing.T) { require.NoError(t, err) var decoded HookExecution - err = json.Unmarshal(data, &decoded) - require.NoError(t, err) - + require.NoError(t, json.Unmarshal(data, &decoded)) assert.Equal(t, original.StartedAt.Unix(), decoded.StartedAt.Unix()) assert.Equal(t, original.CompletedAt.Unix(), decoded.CompletedAt.Unix()) assert.Equal(t, original.Phase, decoded.Phase) @@ -208,12 +206,11 @@ func TestHookExecutionEmptyStringRoundTrip(t *testing.T) { input := `{"started_at":"","completed_at":"","phase":"Succeeded"}` var exec HookExecution - err := json.Unmarshal([]byte(input), &exec) - require.NoError(t, err) + require.NoError(t, json.Unmarshal([]byte(input), &exec)) // Verify time fields are zero values - assert.True(t, exec.StartedAt.IsZero()) - assert.True(t, exec.CompletedAt.IsZero()) + assert.Zero(t, exec.StartedAt) + assert.Zero(t, exec.CompletedAt) assert.Equal(t, HookPhaseSucceeded, exec.Phase) // Marshal back and verify empty time fields are omitted @@ -221,8 +218,7 @@ func TestHookExecutionEmptyStringRoundTrip(t *testing.T) { require.NoError(t, err) var result map[string]any - err = json.Unmarshal(data, &result) - require.NoError(t, err) + require.NoError(t, json.Unmarshal(data, &result)) // Zero time values should be omitted assert.NotContains(t, result, "started_at") diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go index ab7b261cf..c366d106d 100644 --- a/pkg/storage/driver/mock_test.go +++ b/pkg/storage/driver/mock_test.go @@ -24,6 +24,7 @@ import ( sqlmock "github.com/DATA-DOG/go-sqlmock" sq "github.com/Masterminds/squirrel" "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -74,10 +75,7 @@ func tsFixtureMemory(t *testing.T) *Memory { mem := NewMemory() for _, tt := range hs { - err := mem.Create(testKey(tt.Name, tt.Version), tt) - if err != nil { - t.Fatalf("Test setup failed to create: %s\n", err) - } + require.NoError(t, mem.Create(testKey(tt.Name, tt.Version), tt), "Test setup failed to create") } return mem } @@ -108,9 +106,7 @@ func (mock *MockConfigMapsInterface) Init(t *testing.T, releases ...*rspb.Releas objkey := testKey(rls.Name, rls.Version) cfgmap, err := newConfigMapsObject(objkey, rls, nil) - if err != nil { - t.Fatalf("Failed to create configmap: %s", err) - } + require.NoError(t, err, "Failed to create configmap") mock.objects[objkey] = cfgmap } } @@ -196,9 +192,7 @@ func (mock *MockSecretsInterface) Init(t *testing.T, releases ...*rspb.Release) objkey := testKey(rls.Name, rls.Version) secret, err := newSecretsObject(objkey, rls, nil) - if err != nil { - t.Fatalf("Failed to create secret: %s", err) - } + require.NoError(t, err, "Failed to create secret") mock.objects[objkey] = secret } } @@ -262,9 +256,7 @@ func (mock *MockSecretsInterface) Delete(_ context.Context, name string, _ metav func newTestFixtureSQL(t *testing.T, _ ...*rspb.Release) (*SQL, sqlmock.Sqlmock) { t.Helper() sqlDB, mock, err := sqlmock.New() - if err != nil { - t.Fatalf("error when opening stub database connection: %v", err) - } + require.NoError(t, err, "error when opening stub database connection") sqlxDB := sqlx.NewDb(sqlDB, "sqlmock") return &SQL{ diff --git a/pkg/storage/driver/util_test.go b/pkg/storage/driver/util_test.go index d16043924..7074f252a 100644 --- a/pkg/storage/driver/util_test.go +++ b/pkg/storage/driver/util_test.go @@ -16,12 +16,13 @@ package driver import ( "reflect" "testing" + + "github.com/stretchr/testify/assert" ) func TestGetSystemLabel(t *testing.T) { - if output := GetSystemLabels(); !reflect.DeepEqual(systemLabels, output) { - t.Errorf("Expected {%v}, got {%v}", systemLabels, output) - } + output := GetSystemLabels() + assert.Truef(t, reflect.DeepEqual(systemLabels, output), "Expected {%v}, got {%v}", systemLabels, output) } func TestIsSystemLabel(t *testing.T) { @@ -32,9 +33,8 @@ func TestIsSystemLabel(t *testing.T) { "NaMe": false, } for label, result := range tests { - if output := isSystemLabel(label); output != result { - t.Errorf("Output %t not equal to expected %t", output, result) - } + output := isSystemLabel(label) + assert.Equalf(t, output, result, "Output %t not equal to expected %t", output, result) } } @@ -68,9 +68,8 @@ func TestFilterSystemLabels(t *testing.T) { }}, } for _, test := range tests { - if output := filterSystemLabels(test[0]); !reflect.DeepEqual(test[1], output) { - t.Errorf("Expected {%v}, got {%v}", test[1], output) - } + output := filterSystemLabels(test[0]) + assert.Truef(t, reflect.DeepEqual(test[1], output), "Expected {%v}, got {%v}", test[1], output) } } @@ -101,8 +100,7 @@ func TestContainsSystemLabels(t *testing.T) { }, false}, } for _, test := range tests { - if output := ContainsSystemLabels(test.input); !reflect.DeepEqual(test.output, output) { - t.Errorf("Expected {%v}, got {%v}", test.output, output) - } + output := ContainsSystemLabels(test.input) + assert.Truef(t, reflect.DeepEqual(test.output, output), "Expected {%v}, got {%v}", test.output, output) } } From 0b1aa55cbb14ce3b7c805a6262e2159c4a70bf73 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:57 +0200 Subject: [PATCH 477/571] chore(pkg): refactor: convert tests to testify assert/require part 22 #### Description refactor: convert tests to testify assert/require in pkg/action Signed-off-by: Matthieu MOREL --- pkg/action/history_test.go | 4 +--- pkg/action/hooks_test.go | 17 ++++++----------- pkg/action/release_testing_test.go | 12 ++++-------- pkg/chart/v2/dependency_test.go | 10 ++++++---- pkg/chart/v2/lint/rules/deprecations_test.go | 11 ++++------- pkg/chart/v2/util/compatible_test.go | 10 ++++++---- pkg/cmd/helpers_test.go | 16 ++++++---------- pkg/cmd/show_test.go | 12 +++++------- pkg/registry/client_http_test.go | 10 ++++------ pkg/registry/registry_test.go | 10 ++++------ 10 files changed, 46 insertions(+), 66 deletions(-) diff --git a/pkg/action/history_test.go b/pkg/action/history_test.go index 31fdd4a96..51212b610 100644 --- a/pkg/action/history_test.go +++ b/pkg/action/history_test.go @@ -50,9 +50,7 @@ func TestHistoryRun(t *testing.T) { client.Max = 3 client.cfg.Releases.MaxHistory = 3 for _, rel := range []*release.Release{simpleRelease, updatedRelease} { - if err := client.cfg.Releases.Create(rel); err != nil { - t.Fatal(err, "Could not add releases to Config") - } + require.NoError(t, client.cfg.Releases.Create(rel), "Could not add releases to Config") } releases, err := config.Releases.ListReleases() diff --git a/pkg/action/hooks_test.go b/pkg/action/hooks_test.go index 20442af13..ad3a4b618 100644 --- a/pkg/action/hooks_test.go +++ b/pkg/action/hooks_test.go @@ -401,16 +401,12 @@ data: serverSideApply := true err := configuration.execHook(&tc.inputRelease, hookEvent, kube.StatusWatcherStrategy, nil, 600, serverSideApply) - if !reflect.DeepEqual(kubeClient.deleteRecord, tc.expectedDeleteRecord) { - t.Fatalf("Got unexpected delete record, expected: %#v, but got: %#v", kubeClient.deleteRecord, tc.expectedDeleteRecord) - } - - if err != nil && !tc.expectError { - t.Fatal("Got an unexpected error.") - } + require.Truef(t, reflect.DeepEqual(kubeClient.deleteRecord, tc.expectedDeleteRecord), "Got unexpected delete record, expected: %#v, but got: %#v", kubeClient.deleteRecord, tc.expectedDeleteRecord) - if err == nil && tc.expectError { - t.Fatal("Expected and error but did not get it.") + if !tc.expectError { + require.NoError(t, err) + } else { + require.Error(t, err) } }) } @@ -490,8 +486,7 @@ data: ctx := context.Background() waitOptions := []kube.WaitOption{kube.WithWaitContext(ctx)} - err := configuration.execHook(rel, release.HookPreInstall, kube.StatusWatcherStrategy, waitOptions, 600, false) - req.NoError(err) + req.NoError(configuration.execHook(rel, release.HookPreInstall, kube.StatusWatcherStrategy, waitOptions, 600, false)) // Verify that WaitOptions were passed to GetWaiter is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") diff --git a/pkg/action/release_testing_test.go b/pkg/action/release_testing_test.go index 48a3251a1..91cb548a5 100644 --- a/pkg/action/release_testing_test.go +++ b/pkg/action/release_testing_test.go @@ -190,8 +190,7 @@ func TestGetContainerLogs_MultipleContainers(t *testing.T) { rt := &ReleaseTesting{Namespace: "default"} var buf bytes.Buffer - err := rt.getContainerLogs(&buf, client, "test-pod") - require.NoError(t, err) + require.NoError(t, rt.getContainerLogs(&buf, client, "test-pod")) output := buf.String() assert.Contains(t, output, "POD LOGS: test-pod (main)") assert.Contains(t, output, "POD LOGS: test-pod (sidecar)") @@ -217,8 +216,7 @@ func TestGetContainerLogs_WithInitContainers(t *testing.T) { rt := &ReleaseTesting{Namespace: "default"} var buf bytes.Buffer - err := rt.getContainerLogs(&buf, client, "test-pod") - require.NoError(t, err) + require.NoError(t, rt.getContainerLogs(&buf, client, "test-pod")) output := buf.String() // Init containers should appear before regular containers assert.Contains(t, output, "POD LOGS: test-pod (init-setup)") @@ -230,8 +228,7 @@ func TestGetContainerLogs_PodNotFound(t *testing.T) { rt := &ReleaseTesting{Namespace: "default"} var buf bytes.Buffer - err := rt.getContainerLogs(&buf, client, "nonexistent-pod") - assert.ErrorContains(t, err, "unable to get pod nonexistent-pod") + assert.ErrorContains(t, rt.getContainerLogs(&buf, client, "nonexistent-pod"), "unable to get pod nonexistent-pod") } func TestGetContainerLogs_OutputHeaderFormat(t *testing.T) { @@ -252,8 +249,7 @@ func TestGetContainerLogs_OutputHeaderFormat(t *testing.T) { rt := &ReleaseTesting{Namespace: "default"} var buf bytes.Buffer - err := rt.getContainerLogs(&buf, client, "multi-test") - require.NoError(t, err) + require.NoError(t, rt.getContainerLogs(&buf, client, "multi-test")) output := buf.String() assert.Contains(t, output, "POD LOGS: multi-test (container-a)") assert.Contains(t, output, "POD LOGS: multi-test (container-b)") diff --git a/pkg/chart/v2/dependency_test.go b/pkg/chart/v2/dependency_test.go index 35919bd7a..f44a6b06f 100644 --- a/pkg/chart/v2/dependency_test.go +++ b/pkg/chart/v2/dependency_test.go @@ -17,6 +17,8 @@ package v2 import ( "testing" + + "github.com/stretchr/testify/require" ) func TestValidateDependency(t *testing.T) { @@ -35,10 +37,10 @@ func TestValidateDependency(t *testing.T) { } { dep.Alias = value res := dep.Validate() - if res != nil && !shouldFail { - t.Errorf("Failed on case %q", dep.Alias) - } else if res == nil && shouldFail { - t.Errorf("Expected failure for %q", dep.Alias) + if shouldFail { + require.Errorf(t, res, "Expected failure for %q", dep.Alias) + } else { + require.NoErrorf(t, res, "Failed on case %q", dep.Alias) } } } diff --git a/pkg/chart/v2/lint/rules/deprecations_test.go b/pkg/chart/v2/lint/rules/deprecations_test.go index 0aa983678..3c8553cce 100644 --- a/pkg/chart/v2/lint/rules/deprecations_test.go +++ b/pkg/chart/v2/lint/rules/deprecations_test.go @@ -19,6 +19,7 @@ package rules import ( "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -28,17 +29,13 @@ func TestValidateNoDeprecations(t *testing.T) { Kind: "Deployment", } err := validateNoDeprecations(deprecated, nil) - if err == nil { - t.Fatal("Expected deprecated extension to be flagged") - } + require.Error(t, err, "Expected deprecated extension to be flagged") var depErr deprecatedAPIError require.ErrorAs(t, err, &depErr) require.NotEmptyf(t, depErr.Message, "Expected error message to be non-blank") - if err := validateNoDeprecations(&k8sYamlStruct{ + assert.NoError(t, validateNoDeprecations(&k8sYamlStruct{ APIVersion: "v1", Kind: "Pod", - }, nil); err != nil { - t.Error("Expected a v1 Pod to not be deprecated") - } + }, nil), "Expected a v1 Pod to not be deprecated") } diff --git a/pkg/chart/v2/util/compatible_test.go b/pkg/chart/v2/util/compatible_test.go index e17d33e35..9a57ef64a 100644 --- a/pkg/chart/v2/util/compatible_test.go +++ b/pkg/chart/v2/util/compatible_test.go @@ -17,7 +17,11 @@ limitations under the License. // Package version represents the current version of the project. package util -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" +) func TestIsCompatibleRange(t *testing.T) { tests := []struct { @@ -36,8 +40,6 @@ func TestIsCompatibleRange(t *testing.T) { } for _, tt := range tests { - if IsCompatibleRange(tt.constraint, tt.ver) != tt.expected { - t.Errorf("expected constraint %s to be %v for %s", tt.constraint, tt.expected, tt.ver) - } + assert.Equal(t, tt.expected, IsCompatibleRange(tt.constraint, tt.ver), "expected constraint %s to be %v for %s", tt.constraint, tt.expected, tt.ver) } } diff --git a/pkg/cmd/helpers_test.go b/pkg/cmd/helpers_test.go index 3db5f38ab..611d1a54c 100644 --- a/pkg/cmd/helpers_test.go +++ b/pkg/cmd/helpers_test.go @@ -56,17 +56,14 @@ func runTestCmd(t *testing.T, tests []cmdTestCase) { storage := storageFixture() for _, rel := range tt.rels { - if err := storage.Create(rel); err != nil { - t.Fatal(err) - } + require.NoError(t, storage.Create(rel)) } t.Logf("running cmd (attempt %d): %s", i+1, tt.cmd) _, out, err := executeActionCommandC(storage, tt.cmd) - if tt.wantError && err == nil { - t.Errorf("expected error, got success with the following output:\n%s", out) - } - if !tt.wantError && err != nil { - t.Errorf("expected no error, got: '%v'", err) + if tt.wantError { + require.Error(t, err, "expected error, got success with the following output:\n%s", out) + } else { + require.NoError(t, err, "expected no error") } if tt.golden != "" { test.AssertGoldenString(t, out, tt.golden) @@ -294,8 +291,7 @@ func TestCmdGetDryRunFlagStrategy(t *testing.T) { if tc.ExpectedLog != nil { logResult := map[string]string{} - err = json.Unmarshal(logBuf.Bytes(), &logResult) - require.NoError(t, err) + require.NoError(t, json.Unmarshal(logBuf.Bytes(), &logResult)) assert.Equal(t, tc.ExpectedLog.Level, logResult["level"]) assert.Equal(t, tc.ExpectedLog.Msg, logResult["msg"]) diff --git a/pkg/cmd/show_test.go b/pkg/cmd/show_test.go index e24b8c442..3ed01d418 100644 --- a/pkg/cmd/show_test.go +++ b/pkg/cmd/show_test.go @@ -19,9 +19,11 @@ package cmd import ( "fmt" "path/filepath" - "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/repo/v1/repotest" ) @@ -32,9 +34,7 @@ func TestShowPreReleaseChart(t *testing.T) { ) defer srv.Stop() - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.LinkIndices()) tests := []struct { name string @@ -79,9 +79,7 @@ func TestShowPreReleaseChart(t *testing.T) { _, _, err := executeActionCommand(cmd) if err != nil { if tt.fail { - if !strings.Contains(err.Error(), tt.expectedErr) { - t.Errorf("%q expected error: %s, got: %s", tt.name, tt.expectedErr, err.Error()) - } + assert.ErrorContains(t, err, tt.expectedErr, "%q expected error: %s, got: %s", tt.name, tt.expectedErr, err.Error()) return } t.Errorf("%q reported error: %s", tt.name, err) diff --git a/pkg/registry/client_http_test.go b/pkg/registry/client_http_test.go index 316a82d3f..0a51b3347 100644 --- a/pkg/registry/client_http_test.go +++ b/pkg/registry/client_http_test.go @@ -39,15 +39,13 @@ func (suite *HTTPRegistryClientTestSuite) TearDownSuite() { } func (suite *HTTPRegistryClientTestSuite) Test_0_Login() { - err := suite.RegistryClient.Login(suite.DockerRegistryHost, + suite.Require().Error(suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth("badverybad", "ohsobad"), - LoginOptPlainText(true)) - suite.Require().Error(err, "error logging into registry with bad credentials") + LoginOptPlainText(true)), "error logging into registry with bad credentials") - err = suite.RegistryClient.Login(suite.DockerRegistryHost, + suite.Require().NoError(suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth(testUsername, testPassword), - LoginOptPlainText(true)) - suite.Require().NoError(err, "no error logging into registry with good credentials") + LoginOptPlainText(true)), "no error logging into registry with good credentials") } func (suite *HTTPRegistryClientTestSuite) Test_1_Push() { diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index 429b15113..313cb861c 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -73,10 +73,8 @@ type TestRegistry struct { func setup(suite *TestRegistry, tlsEnabled, insecure bool, auth string) { suite.WorkspaceDir = testWorkspaceDir - err := os.RemoveAll(suite.WorkspaceDir) - suite.Require().NoError(err, "no error removing test workspace dir") - err = os.Mkdir(suite.WorkspaceDir, 0o700) - suite.Require().NoError(err, "no error creating test workspace dir") + suite.Require().NoError(os.RemoveAll(suite.WorkspaceDir), "no error removing test workspace dir") + suite.Require().NoError(os.Mkdir(suite.WorkspaceDir, 0o700), "no error creating test workspace dir") var out bytes.Buffer @@ -92,6 +90,7 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool, auth string) { ClientOptBasicAuth(testUsername, testPassword), } + var err error if tlsEnabled { var tlsConf *tls.Config if insecure { @@ -122,8 +121,7 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool, auth string) { pwBytes, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost) suite.Require().NoError(err, "no error generating bcrypt password for test htpasswd file") htpasswdPath := filepath.Join(suite.WorkspaceDir, testHtpasswdFileBasename) - err = os.WriteFile(htpasswdPath, fmt.Appendf(nil, "%s:%s\n", testUsername, string(pwBytes)), 0o644) - suite.Require().NoError(err, "no error creating test htpasswd file") + suite.Require().NoError(os.WriteFile(htpasswdPath, fmt.Appendf(nil, "%s:%s\n", testUsername, string(pwBytes)), 0o644), "no error creating test htpasswd file") // Registry config config := &configuration.Configuration{} From 33c28395695c53b579c1f94d3dfcadbe2ff2b862 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 Jul 2026 20:50:56 +0200 Subject: [PATCH 478/571] chore(pkg): refactor: convert tests to testify assert/require part 19 refactor: convert tests to testify assert/require in pkg/action Signed-off-by: Matthieu MOREL --- pkg/action/lint_test.go | 46 +++++++++------------- pkg/action/show_test.go | 41 ++++++-------------- pkg/action/upgrade_test.go | 40 ++++++++----------- pkg/action/validate_test.go | 37 ++++++------------ pkg/chart/v2/lint/rules/values_test.go | 44 ++++++--------------- pkg/cmd/install_test.go | 6 +-- pkg/cmd/plugin_test.go | 48 +++++++---------------- pkg/getter/getter_test.go | 53 ++++++++++---------------- pkg/storage/driver/records_test.go | 53 ++++++++++---------------- 9 files changed, 125 insertions(+), 243 deletions(-) diff --git a/pkg/action/lint_test.go b/pkg/action/lint_test.go index 5b5c2c50a..5c956c682 100644 --- a/pkg/action/lint_test.go +++ b/pkg/action/lint_test.go @@ -21,6 +21,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/chart/v2/lint/support" ) @@ -89,11 +90,10 @@ func TestLintChart(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := lintChart(tt.chartPath, map[string]any{}, namespace, nil, tt.skipSchemaValidation) - switch { - case err != nil && !tt.err: - t.Errorf("%s", err) - case err == nil && tt.err: - t.Error("Expected a chart parsing error") + if tt.err { + require.Error(t, err, "Expected a chart parsing error") + } else { + require.NoError(t, err) } }) } @@ -106,14 +106,10 @@ func TestNonExistentChart(t *testing.T) { testLint := NewLint() result := testLint.Run(testCharts, values) - if len(result.Errors) != 1 { - t.Error("expected one error, but got", len(result.Errors)) - } + assert.Len(t, result.Errors, 1, "expected one error, but got", len(result.Errors)) actual := result.Errors[0].Error() - if actual != expectedError { - t.Errorf("expected '%s', but got '%s'", expectedError, actual) - } + assert.EqualError(t, result.Errors[0], expectedError, "expected '%s', but got '%s'", expectedError, actual) }) t.Run("should error out for corrupted tgz chart", func(t *testing.T) { @@ -122,31 +118,25 @@ func TestNonExistentChart(t *testing.T) { testLint := NewLint() result := testLint.Run(testCharts, values) - if len(result.Errors) != 1 { - t.Error("expected one error, but got", len(result.Errors)) - } + assert.Len(t, result.Errors, 1, "expected one error, but got", len(result.Errors)) actual := result.Errors[0].Error() - if actual != expectedEOFError { - t.Errorf("expected '%s', but got '%s'", expectedEOFError, actual) - } + assert.EqualError(t, result.Errors[0], expectedEOFError, "expected '%s', but got '%s'", expectedEOFError, actual) }) } func TestLint_MultipleCharts(t *testing.T) { testCharts := []string{chart2MultipleChartLint, chart1MultipleChartLint} testLint := NewLint() - if result := testLint.Run(testCharts, values); len(result.Errors) > 0 { - t.Error(result.Errors) - } + result := testLint.Run(testCharts, values) + assert.Empty(t, result.Errors) } func TestLint_EmptyResultErrors(t *testing.T) { testCharts := []string{chart2MultipleChartLint} testLint := NewLint() - if result := testLint.Run(testCharts, values); len(result.Errors) > 0 { - t.Error("Expected no error, got more") - } + result := testLint.Run(testCharts, values) + assert.Empty(t, result.Errors, "Expected no error, got more") } func TestLint_ChartWithWarnings(t *testing.T) { @@ -154,18 +144,16 @@ func TestLint_ChartWithWarnings(t *testing.T) { testCharts := []string{chartWithNoTemplatesDir} testLint := NewLint() testLint.Strict = false - if result := testLint.Run(testCharts, values); len(result.Errors) > 0 { - t.Error("Expected no error, got more") - } + result := testLint.Run(testCharts, values) + assert.Empty(t, result.Errors, "Expected no error, got more") }) t.Run("should fail with one error when strict", func(t *testing.T) { testCharts := []string{chartWithNoTemplatesDir} testLint := NewLint() testLint.Strict = true - if result := testLint.Run(testCharts, values); len(result.Errors) != 1 { - t.Error("expected one error, but got", len(result.Errors)) - } + result := testLint.Run(testCharts, values) + assert.Len(t, result.Errors, 1, "expected one error") }) } diff --git a/pkg/action/show_test.go b/pkg/action/show_test.go index 0dde7fb74..c6c55ead1 100644 --- a/pkg/action/show_test.go +++ b/pkg/action/show_test.go @@ -21,6 +21,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/chart/common" chart "helm.sh/helm/v4/pkg/chart/v2" @@ -47,9 +48,7 @@ func TestShow(t *testing.T) { } output, err := client.Run("") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) expect := `name: alpine @@ -69,9 +68,7 @@ bar baz ` - if output != expect { - t.Errorf("Expected\n%q\nGot\n%q\n", expect, output) - } + assert.Equal(t, expect, output, "Expected\n%q\nGot\n%q\n", expect, output) } func TestShowNoValues(t *testing.T) { @@ -82,13 +79,9 @@ func TestShowNoValues(t *testing.T) { // Regression tests for missing values. See issue #1024. client.OutputFormat = ShowValues output, err := client.Run("") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if output != "" { - t.Errorf("expected empty values buffer, got %s", output) - } + assert.Empty(t, output, "expected empty values buffer, got %s", output) } func TestShowValuesByJsonPathFormat(t *testing.T) { @@ -97,13 +90,9 @@ func TestShowValuesByJsonPathFormat(t *testing.T) { client.JSONPathTemplate = "{$.nestedKey.simpleKey}" client.chart = buildChart(withSampleValues()) output, err := client.Run("") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) expect := "simpleValue" - if output != expect { - t.Errorf("Expected\n%q\nGot\n%q\n", expect, output) - } + assert.Equal(t, expect, output, "Expected\n%q\nGot\n%q\n", expect, output) } func TestShowCRDs(t *testing.T) { @@ -121,9 +110,7 @@ func TestShowCRDs(t *testing.T) { } output, err := client.Run("") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) expect := `--- foo @@ -135,9 +122,7 @@ bar baz ` - if output != expect { - t.Errorf("Expected\n%q\nGot\n%q\n", expect, output) - } + assert.Equal(t, expect, output, "Expected\n%q\nGot\n%q\n", expect, output) } func TestShowNoReadme(t *testing.T) { @@ -154,9 +139,7 @@ func TestShowNoReadme(t *testing.T) { } output, err := client.Run("") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) expect := `name: alpine @@ -167,9 +150,7 @@ foo bar ` - if output != expect { - t.Errorf("Expected\n%q\nGot\n%q\n", expect, output) - } + assert.Equal(t, expect, output, "Expected\n%q\nGot\n%q\n", expect, output) } func TestShowSetRegistryClient(t *testing.T) { diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 7a73c7179..b7e25206d 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -176,8 +176,8 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) { resi, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) - is.Contains(err.Error(), "arming key removed") - is.Contains(err.Error(), "rollback-on-failure") + req.ErrorContains(err, "arming key removed") + req.ErrorContains(err, "rollback-on-failure") res, err := releaserToV1Release(resi) req.NoError(err) @@ -207,8 +207,8 @@ func TestUpgradeRelease_RollbackOnFailure(t *testing.T) { _, err := upAction.Run(rel.Name, buildChart(), vals) req.Error(err) - is.Contains(err.Error(), "update fail") - is.Contains(err.Error(), "an error occurred while rolling back the release") + req.ErrorContains(err, "update fail") + is.ErrorContains(err, "an error occurred while rolling back the release") }) } @@ -240,8 +240,7 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { rel.Info.Status = common.StatusDeployed rel.Config = existingValues - err := upAction.cfg.Releases.Create(rel) - req.NoError(err) + req.NoError(upAction.cfg.Releases.Create(rel)) upAction.ReuseValues = true // setting newValues and upgrading @@ -303,8 +302,7 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { Config: existingValues, Version: 1, } - err := upAction.cfg.Releases.Create(rel) - req.NoError(err) + req.NoError(upAction.cfg.Releases.Create(rel)) upAction.ReuseValues = true sampleChartWithSubChart := buildChart( @@ -373,8 +371,7 @@ func TestUpgradeRelease_ResetThenReuseValues(t *testing.T) { rel.Info.Status = common.StatusDeployed rel.Config = existingValues - err := upAction.cfg.Releases.Create(rel) - req.NoError(err) + req.NoError(upAction.cfg.Releases.Create(rel)) upAction.ResetThenReuseValues = true // setting newValues and upgrading @@ -417,7 +414,7 @@ func TestUpgradeRelease_Pending(t *testing.T) { vals := map[string]any{} _, err := upAction.Run(rel.Name, buildChart(), vals) - req.Contains(err.Error(), "progress", err) + req.ErrorContains(err, "progress") } func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { @@ -470,7 +467,7 @@ func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) { resi, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) req.Error(err) - is.Contains(err.Error(), "release interrupted-release failed, and has been rolled back due to rollback-on-failure being set: context canceled") + req.ErrorContains(err, "release interrupted-release failed, and has been rolled back due to rollback-on-failure being set: context canceled") res, err := releaserToV1Release(resi) req.NoError(err) // Now make sure it is actually upgraded @@ -491,9 +488,8 @@ func TestMergeCustomLabels(t *testing.T) { {map[string]string{"k1": "v1", "k2": "v2"}, map[string]string{"k1": "null", "k2": "v3"}, map[string]string{"k2": "v3"}}, } for _, test := range tests { - if output := mergeCustomLabels(test[0], test[1]); !reflect.DeepEqual(test[2], output) { - t.Errorf("Expected {%v}, got {%v}", test[2], output) - } + output := mergeCustomLabels(test[0], test[1]) + assert.Truef(t, reflect.DeepEqual(test[2], output), "Expected {%v}, got {%v}", test[2], output) } } @@ -511,8 +507,7 @@ func TestUpgradeRelease_Labels(t *testing.T) { } rel.Info.Status = common.StatusDeployed - err := upAction.cfg.Releases.Create(rel) - req.NoError(err) + req.NoError(upAction.cfg.Releases.Create(rel)) upAction.Labels = map[string]string{ "key1": "null", @@ -566,8 +561,7 @@ func TestUpgradeRelease_SystemLabels(t *testing.T) { } rel.Info.Status = common.StatusDeployed - err := upAction.cfg.Releases.Create(rel) - req.NoError(err) + req.NoError(upAction.cfg.Releases.Create(rel)) upAction.Labels = map[string]string{ "key1": "null", @@ -575,12 +569,10 @@ func TestUpgradeRelease_SystemLabels(t *testing.T) { "owner": "val3", } // setting newValues and upgrading - _, err = upAction.Run(rel.Name, buildChart(), nil) - if err == nil { - t.Fatal("expected an error") - } + _, err := upAction.Run(rel.Name, buildChart(), nil) + require.Error(t, err, "expected an error") - is.Equal(fmt.Errorf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()), err) + is.EqualError(err, fmt.Sprintf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels())) } func TestUpgradeRelease_DryRun(t *testing.T) { diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go index 878bec599..27097d177 100644 --- a/pkg/action/validate_test.go +++ b/pkg/action/validate_test.go @@ -171,45 +171,38 @@ func TestCheckOwnership(t *testing.T) { deployFoo := newDeploymentResource("foo", "ns-a", "") // Verify that a resource that lacks labels/annotations is not owned - err := checkOwnership(deployFoo.Object, "rel-a", "ns-a") - require.EqualError(t, err, `invalid ownership metadata; label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm"; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) + require.EqualError(t, checkOwnership(deployFoo.Object, "rel-a", "ns-a"), `invalid ownership metadata; label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm"; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) // Set managed by label and verify annotation error message _ = accessor.SetLabels(deployFoo.Object, map[string]string{ appManagedByLabel: appManagedByHelm, }) - err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") - require.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) + require.EqualError(t, checkOwnership(deployFoo.Object, "rel-a", "ns-a"), `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) // Set only the release name annotation and verify missing release namespace error message _ = accessor.SetAnnotations(deployFoo.Object, map[string]string{ helmReleaseNameAnnotation: "rel-a", }) - err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") - require.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) + require.EqualError(t, checkOwnership(deployFoo.Object, "rel-a", "ns-a"), `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) // Set both release name and namespace annotations and verify no ownership errors _ = accessor.SetAnnotations(deployFoo.Object, map[string]string{ helmReleaseNameAnnotation: "rel-a", helmReleaseNamespaceAnnotation: "ns-a", }) - err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") - require.NoError(t, err) + require.NoError(t, checkOwnership(deployFoo.Object, "rel-a", "ns-a")) // Verify ownership error for wrong release name - err = checkOwnership(deployFoo.Object, "rel-b", "ns-a") - require.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-name" must equal "rel-b": current value is "rel-a"`) + require.EqualError(t, checkOwnership(deployFoo.Object, "rel-b", "ns-a"), `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-name" must equal "rel-b": current value is "rel-a"`) // Verify ownership error for wrong release namespace - err = checkOwnership(deployFoo.Object, "rel-a", "ns-b") - require.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-namespace" must equal "ns-b": current value is "ns-a"`) + require.EqualError(t, checkOwnership(deployFoo.Object, "rel-a", "ns-b"), `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-namespace" must equal "ns-b": current value is "ns-a"`) // Verify ownership error for wrong manager label _ = accessor.SetLabels(deployFoo.Object, map[string]string{ appManagedByLabel: "helm", }) - err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") - assert.EqualError(t, err, `invalid ownership metadata; label validation error: key "app.kubernetes.io/managed-by" must equal "Helm": current value is "helm"`) + assert.EqualError(t, checkOwnership(deployFoo.Object, "rel-a", "ns-a"), `invalid ownership metadata; label validation error: key "app.kubernetes.io/managed-by" must equal "Helm": current value is "helm"`) } func TestVerifyOwnershipBeforeDelete(t *testing.T) { @@ -306,32 +299,26 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { func TestSetMetadataVisitor(t *testing.T) { var ( - err error deployFoo = newDeploymentResource("foo", "ns-a", "") deployBar = newDeploymentResource("bar", "ns-a-system", "") resources = kube.ResourceList{deployFoo, deployBar} ) // Set release tracking metadata and verify no error - err = resources.Visit(setMetadataVisitor("rel-a", "ns-a", true)) - require.NoError(t, err) + require.NoError(t, resources.Visit(setMetadataVisitor("rel-a", "ns-a", true))) // Verify that release "b" cannot take ownership of "a" - err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) - require.Error(t, err) + require.Error(t, resources.Visit(setMetadataVisitor("rel-b", "ns-a", false))) // Force release "b" to take ownership - err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", true)) - require.NoError(t, err) + require.NoError(t, resources.Visit(setMetadataVisitor("rel-b", "ns-a", true))) // Check that there is now no ownership error when setting metadata without force - err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) - require.NoError(t, err) + require.NoError(t, resources.Visit(setMetadataVisitor("rel-b", "ns-a", false))) // Add a new resource that is missing ownership metadata and verify error resources.Append(newDeploymentResource("baz", "default", "")) - err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) - assert.ErrorContains(t, err, `Deployment "baz" in namespace "" cannot be owned`) + assert.ErrorContains(t, resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)), `Deployment "baz" in namespace "" cannot be owned`) } func TestValidateNameAndGenerateName(t *testing.T) { diff --git a/pkg/chart/v2/lint/rules/values_test.go b/pkg/chart/v2/lint/rules/values_test.go index 470904049..82e8e660e 100644 --- a/pkg/chart/v2/lint/rules/values_test.go +++ b/pkg/chart/v2/lint/rules/values_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/internal/test/ensure" ) @@ -54,11 +55,7 @@ const testSchema = ` func TestValidateValuesYamlNotDirectory(t *testing.T) { _ = os.Mkdir(nonExistingValuesFilePath, os.ModePerm) defer os.Remove(nonExistingValuesFilePath) - - err := validateValuesFileExistence(nonExistingValuesFilePath) - if err == nil { - t.Error("validateValuesFileExistence to return a linter error, got no error") - } + assert.Error(t, validateValuesFileExistence(nonExistingValuesFilePath), "validateValuesFileExistence to return a linter error, got no error") } func TestValidateValuesFileWellFormed(t *testing.T) { @@ -67,9 +64,7 @@ func TestValidateValuesFileWellFormed(t *testing.T) { ` tmpdir := ensure.TempFile(t, "values.yaml", []byte(badYaml)) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]any{}, false); err == nil { - t.Fatal("expected values file to fail parsing") - } + require.Error(t, validateValuesFile(valfile, map[string]any{}, false), "expected values file to fail parsing") } func TestValidateValuesFileSchema(t *testing.T) { @@ -78,9 +73,7 @@ func TestValidateValuesFileSchema(t *testing.T) { createTestingSchema(t, tmpdir) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]any{}, false); err != nil { - t.Fatalf("Failed validation with %s", err) - } + require.NoErrorf(t, validateValuesFile(valfile, map[string]any{}, false), "Failed validation") } func TestValidateValuesFileSchemaFailure(t *testing.T) { @@ -90,9 +83,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) { createTestingSchema(t, tmpdir) valfile := filepath.Join(tmpdir, "values.yaml") - - err := validateValuesFile(valfile, map[string]any{}, false) - assert.ErrorContains(t, err, "- at '/username': got number, want string") + assert.ErrorContains(t, validateValuesFile(valfile, map[string]any{}, false), "- at '/username': got number, want string") } func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T) { @@ -102,11 +93,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T createTestingSchema(t, tmpdir) valfile := filepath.Join(tmpdir, "values.yaml") - - err := validateValuesFile(valfile, map[string]any{}, true) - if err != nil { - t.Fatal("expected values file to pass parsing because of skipSchemaValidation") - } + require.NoError(t, validateValuesFile(valfile, map[string]any{}, true), "expected values file to pass parsing because of skipSchemaValidation") } func TestValidateValuesFileSchemaOverrides(t *testing.T) { @@ -118,9 +105,7 @@ func TestValidateValuesFileSchemaOverrides(t *testing.T) { createTestingSchema(t, tmpdir) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, overrides, false); err != nil { - t.Fatalf("Failed validation with %s", err) - } + require.NoErrorf(t, validateValuesFile(valfile, overrides, false), "Failed validation") } func TestValidateValuesFile(t *testing.T) { @@ -157,13 +142,10 @@ func TestValidateValuesFile(t *testing.T) { err := validateValuesFile(valfile, tt.overrides, false) - switch { - case err != nil && tt.errorMessage == "": - t.Errorf("Failed validation with %s", err) - case err == nil && tt.errorMessage != "": - t.Error("expected values file to fail parsing") - case err != nil && tt.errorMessage != "": - assert.ErrorContains(t, err, tt.errorMessage, "Failed with unexpected error") + if tt.errorMessage == "" { + require.NoErrorf(t, err, "Failed validation with") + } else { + assert.ErrorContains(t, err, tt.errorMessage) } }) } @@ -172,8 +154,6 @@ func TestValidateValuesFile(t *testing.T) { func createTestingSchema(t *testing.T, dir string) string { t.Helper() schemafile := filepath.Join(dir, "values.schema.json") - if err := os.WriteFile(schemafile, []byte(testSchema), 0o700); err != nil { - t.Fatalf("Failed to write schema to tmpdir: %s", err) - } + require.NoErrorf(t, os.WriteFile(schemafile, []byte(testSchema), 0o700), "Failed to write schema to tmpdir") return schemafile } diff --git a/pkg/cmd/install_test.go b/pkg/cmd/install_test.go index 8d3435e03..d1e081e5c 100644 --- a/pkg/cmd/install_test.go +++ b/pkg/cmd/install_test.go @@ -23,6 +23,8 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/repo/v1/repotest" ) @@ -39,9 +41,7 @@ func TestInstall(t *testing.T) { })) defer srv2.Close() - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } + require.NoError(t, srv.LinkIndices()) repoFile := filepath.Join(srv.Root(), "repositories.yaml") diff --git a/pkg/cmd/plugin_test.go b/pkg/cmd/plugin_test.go index 5364423a0..6a7980119 100644 --- a/pkg/cmd/plugin_test.go +++ b/pkg/cmd/plugin_test.go @@ -72,14 +72,10 @@ func TestManuallyProcessArgs(t *testing.T) { known, unknown := manuallyProcessArgs(input) for i, k := range known { - if k != expectKnown[i] { - t.Errorf("expected known flag %d to be %q, got %q", i, expectKnown[i], k) - } + assert.Equal(t, expectKnown[i], k, "expected known flag %d to be %q, got %q", i, expectKnown[i], k) } for i, k := range unknown { - if k != expectUnknown[i] { - t.Errorf("expected unknown flag %d to be %q, got %q", i, expectUnknown[i], k) - } + assert.Equal(t, expectUnknown[i], k, "expected unknown flag %d to be %q, got %q", i, expectUnknown[i], k) } } @@ -130,23 +126,17 @@ func TestLoadCLIPlugins(t *testing.T) { pluginCmd := pluginCmds[i] t.Run(fmt.Sprintf("%s-%d", pluginCmd.Name(), i), func(t *testing.T) { out.Reset() - if pluginCmd.Use != tt.use { - t.Errorf("%d: Expected Use=%q, got %q", i, tt.use, pluginCmd.Use) - } - if pluginCmd.Short != tt.short { - t.Errorf("%d: Expected Use=%q, got %q", i, tt.short, pluginCmd.Short) - } - if pluginCmd.Long != tt.long { - t.Errorf("%d: Expected Use=%q, got %q", i, tt.long, pluginCmd.Long) - } + assert.Equal(t, tt.use, pluginCmd.Use, "%d: Expected Use=%q, got %q", i, tt.use, pluginCmd.Use) + assert.Equal(t, tt.short, pluginCmd.Short, "%d: Expected Use=%q, got %q", i, tt.short, pluginCmd.Short) + assert.Equal(t, tt.long, pluginCmd.Long, "%d: Expected Use=%q, got %q", i, tt.long, pluginCmd.Long) // Currently, plugins assume a Linux subsystem. Skip the execution // tests until this is fixed if runtime.GOOS != "windows" { if err := pluginCmd.RunE(pluginCmd, tt.args); err != nil { - if assert.Positive(t, tt.code, "Error running %s: %+v", tt.use, err) { + if assert.Positive(t, tt.code, "Error running %s", tt.use) { var cerr CommandError - require.ErrorAs(t, err, &cerr, "Expected %s to return pluginError: got %v(%T)", tt.use, err, err) + require.ErrorAs(t, err, &cerr, "Expected %s to return pluginError", tt.use) assert.Equalf(t, tt.code, cerr.ExitCode, "Expected %s to return %d: got %d", tt.use, tt.code, cerr.ExitCode) } } @@ -190,31 +180,23 @@ func TestLoadPluginsWithSpace(t *testing.T) { plugins := cmd.Commands() - if len(plugins) != len(tests) { - t.Fatalf("Expected %d plugins, got %d", len(tests), len(plugins)) - } + require.Len(t, plugins, len(tests), "Expected %d plugins, got %d", len(tests), len(plugins)) for i := range plugins { out.Reset() tt := tests[i] pp := plugins[i] - if pp.Use != tt.use { - t.Errorf("%d: Expected Use=%q, got %q", i, tt.use, pp.Use) - } - if pp.Short != tt.short { - t.Errorf("%d: Expected Use=%q, got %q", i, tt.short, pp.Short) - } - if pp.Long != tt.long { - t.Errorf("%d: Expected Use=%q, got %q", i, tt.long, pp.Long) - } + assert.Equal(t, tt.use, pp.Use, "%d: Expected Use=%q, got %q", i, tt.use, pp.Use) + assert.Equal(t, tt.short, pp.Short, "%d: Expected Use=%q, got %q", i, tt.short, pp.Short) + assert.Equal(t, tt.long, pp.Long, "%d: Expected Use=%q, got %q", i, tt.long, pp.Long) // Currently, plugins assume a Linux subsystem. Skip the execution // tests until this is fixed if runtime.GOOS != "windows" { if err := pp.RunE(pp, tt.args); err != nil { - if assert.Positive(t, tt.code, "Error running %s: %+v", tt.use, err) { + if assert.Positive(t, tt.code, "Error running %s", tt.use) { var cerr CommandError - require.ErrorAs(t, err, &cerr, "Expected %s to return pluginError: got %v(%T)", tt.use, err, err) + require.ErrorAs(t, err, &cerr, "Expected %s to return pluginError", tt.use) assert.Equalf(t, tt.code, cerr.ExitCode, "Expected %s to return %d: got %d", tt.use, tt.code, cerr.ExitCode) } } @@ -334,9 +316,7 @@ func TestLoadCLIPlugins_HelmNoPlugins(t *testing.T) { loadCLIPlugins(cmd, out) plugins := cmd.Commands() - if len(plugins) != 0 { - t.Fatalf("Expected 0 plugins, got %d", len(plugins)) - } + require.Empty(t, plugins, "Expected 0 plugins, got %d", len(plugins)) } func TestPluginCmdsCompletion(t *testing.T) { diff --git a/pkg/getter/getter_test.go b/pkg/getter/getter_test.go index 3a09b4d82..67cfc2ef0 100644 --- a/pkg/getter/getter_test.go +++ b/pkg/getter/getter_test.go @@ -19,6 +19,9 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/cli" ) @@ -30,9 +33,7 @@ func TestProvider(t *testing.T) { func(_ ...Option) (Getter, error) { return nil, nil }, } - if !p.Provides("three") { - t.Error("Expected provider to provide three") - } + assert.True(t, p.Provides("three"), "Expected provider to provide three") } func TestProviders(t *testing.T) { @@ -41,34 +42,25 @@ func TestProviders(t *testing.T) { {[]string{"two", "four"}, func(_ ...Option) (Getter, error) { return nil, nil }}, } - if _, err := ps.ByScheme("one"); err != nil { - t.Error(err) - } - if _, err := ps.ByScheme("four"); err != nil { - t.Error(err) - } + _, err := ps.ByScheme("one") + require.NoError(t, err) + _, err = ps.ByScheme("four") + require.NoError(t, err) - if _, err := ps.ByScheme("five"); err == nil { - t.Error("Did not expect handler for five") - } + _, err = ps.ByScheme("five") + assert.Error(t, err, "Did not expect handler for five") } func TestProvidersWithTimeout(t *testing.T) { want := time.Hour getters := Getters(WithTimeout(want)) getter, err := getters.ByScheme("http") - if err != nil { - t.Error(err) - } + require.NoError(t, err) httpGetter := getter.(*HTTPGetter) client, err := httpGetter.httpClient(httpGetter.opts) - if err != nil { - t.Error(err) - } + require.NoError(t, err) got := client.Timeout - if got != want { - t.Errorf("Expected %q, got %q", want, got) - } + assert.Equal(t, want, got, "Expected %q, got %q", want, got) } func TestAll(t *testing.T) { @@ -76,13 +68,10 @@ func TestAll(t *testing.T) { env.PluginsDirectory = pluginDir all := All(env) - if len(all) != 4 { - t.Errorf("expected 4 providers (default plus three plugins), got %d", len(all)) - } + assert.Len(t, all, 4, "expected 4 providers (default plus three plugins), got %d", len(all)) - if _, err := all.ByScheme("test2"); err != nil { - t.Error(err) - } + _, err := all.ByScheme("test2") + assert.NoError(t, err) } func TestByScheme(t *testing.T) { @@ -90,10 +79,8 @@ func TestByScheme(t *testing.T) { env.PluginsDirectory = pluginDir g := All(env) - if _, err := g.ByScheme("test"); err != nil { - t.Error(err) - } - if _, err := g.ByScheme("https"); err != nil { - t.Error(err) - } + _, err := g.ByScheme("test") + require.NoError(t, err) + _, err = g.ByScheme("https") + assert.NoError(t, err) } diff --git a/pkg/storage/driver/records_test.go b/pkg/storage/driver/records_test.go index cd1064342..067e31e76 100644 --- a/pkg/storage/driver/records_test.go +++ b/pkg/storage/driver/records_test.go @@ -20,6 +20,9 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/release/common" ) @@ -50,10 +53,11 @@ func TestRecordsAdd(t *testing.T) { } for _, tt := range tests { - if err := rs.Add(tt.rec); err != nil { - if !tt.ok { - t.Fatalf("failed: %q: %s\n", tt.desc, err) - } + err := rs.Add(tt.rec) + if !tt.ok { + require.NoError(t, err, "failed: %q:", tt.desc) + } else { + require.Error(t, err) } } } @@ -77,23 +81,18 @@ func TestRecordsRemove(t *testing.T) { startLen := rs.Len() for _, tt := range tests { - if r := rs.Remove(tt.key); r == nil { - if !tt.ok { - t.Fatalf("Failed to %q (key = %s). Expected nil, got %v", - tt.desc, - tt.key, - r, - ) - } + r := rs.Remove(tt.key) + if tt.ok { + require.Nil(t, r, "Failed to %q (key = %s). Expected nil, got %v", tt.desc, tt.key, r) + } else { + require.NotNil(t, r) } } // We expect the total number of records will be less now than there were // when we started. endLen := rs.Len() - if endLen >= startLen { - t.Errorf("expected ending length %d to be less than starting length %d", endLen, startLen) - } + assert.Lessf(t, endLen, startLen, "expected ending length %d to be less than starting length %d", endLen, startLen) } func TestRecordsRemoveAt(t *testing.T) { @@ -102,14 +101,10 @@ func TestRecordsRemoveAt(t *testing.T) { newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), }) - if len(rs) != 2 { - t.Fatal("Expected len=2 for mock") - } + require.Len(t, rs, 2, "Expected len=2 for mock") rs.Remove("rls-a.v1") - if len(rs) != 1 { - t.Fatalf("Expected length of rs to be 1, got %d", len(rs)) - } + require.Len(t, rs, 1, "Expected length of rs to be 1, got %d", len(rs)) } func TestRecordsGet(t *testing.T) { @@ -137,9 +132,7 @@ func TestRecordsGet(t *testing.T) { for _, tt := range tests { got := rs.Get(tt.key) - if !reflect.DeepEqual(tt.rec, got) { - t.Fatalf("Expected %v, got %v", tt.rec, got) - } + require.Truef(t, reflect.DeepEqual(tt.rec, got), "Expected %v, got %v", tt.rec, got) } } @@ -168,9 +161,7 @@ func TestRecordsIndex(t *testing.T) { for _, tt := range tests { got, _ := rs.Index(tt.key) - if got != tt.sort { - t.Fatalf("Expected %d, got %d", tt.sort, got) - } + require.Equal(t, tt.sort, got, "Expected %d, got %d", tt.sort, got) } } @@ -199,9 +190,7 @@ func TestRecordsExists(t *testing.T) { for _, tt := range tests { got := rs.Exists(tt.key) - if got != tt.ok { - t.Fatalf("Expected %t, got %t", tt.ok, got) - } + require.Equal(t, tt.ok, got, "Expected %t, got %t", tt.ok, got) } } @@ -233,8 +222,6 @@ func TestRecordsReplace(t *testing.T) { for _, tt := range tests { got := rs.Replace(tt.key, tt.rec) - if !reflect.DeepEqual(tt.expected, got) { - t.Fatalf("Expected %v, got %v", tt.expected, got) - } + require.Truef(t, reflect.DeepEqual(tt.expected, got), "Expected %v, got %v", tt.expected, got) } } From cd4c5d3bfd7d19be9a3a01ba63a650cf044e1409 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Tue, 21 Jul 2026 14:38:44 -0600 Subject: [PATCH 479/571] chore: group github-actions dependabot updates Signed-off-by: Terry Howe --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d8e8e3379..1b1de9e55 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -37,9 +37,17 @@ updates: directory: "/" schedule: interval: "daily" + groups: + github-actions: + patterns: + - "*" - # Keep dev-v3 GitHub Actions up to date, while Helm v3 is within support package-ecosystem: "github-actions" target-branch: "dev-v3" directory: "/" schedule: interval: "daily" + groups: + github-actions: + patterns: + - "*" From 065d3a6a3dcda016a9de88a9b32f0e9eedcf0876 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 21:33:05 +0000 Subject: [PATCH 480/571] chore(deps): bump github/codeql-action/analyze from 4.37.1 to 4.37.2 Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.37.1 to 4.37.2. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/7188fc363630916deb702c7fdcf4e481b751f97a...e0647621c2984b5ed2f768cb892365bf2a616ad1) --- updated-dependencies: - dependency-name: github/codeql-action/analyze dependency-version: 4.37.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d02d55b82..53a76f6f6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # pinv4.37.1 + uses: github/codeql-action/analyze@e0647621c2984b5ed2f768cb892365bf2a616ad1 # pinv4.37.2 From cdccef4818c06e4d1ee9521cfb9bcadc30c282e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 21:33:15 +0000 Subject: [PATCH 481/571] chore(deps): bump github/codeql-action/init from 4.37.1 to 4.37.2 Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.37.1 to 4.37.2. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/7188fc363630916deb702c7fdcf4e481b751f97a...e0647621c2984b5ed2f768cb892365bf2a616ad1) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.37.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d02d55b82..1e7748d44 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # pinv4.37.1 + uses: github/codeql-action/init@e0647621c2984b5ed2f768cb892365bf2a616ad1 # pinv4.37.2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. From a0f954e277702f87883d18760207b34ba3efbf80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 21:33:23 +0000 Subject: [PATCH 482/571] chore(deps): bump github/codeql-action/autobuild from 4.37.1 to 4.37.2 Bumps [github/codeql-action/autobuild](https://github.com/github/codeql-action) from 4.37.1 to 4.37.2. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/7188fc363630916deb702c7fdcf4e481b751f97a...e0647621c2984b5ed2f768cb892365bf2a616ad1) --- updated-dependencies: - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d02d55b82..d28011b0a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@7188fc363630916deb702c7fdcf4e481b751f97a # pinv4.37.1 + uses: github/codeql-action/autobuild@e0647621c2984b5ed2f768cb892365bf2a616ad1 # pinv4.37.2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl From 5223ceacda672bdd72efe8995d4abd9327c6539c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 16:08:04 -0600 Subject: [PATCH 483/571] chore(deps): bump github/codeql-action/upload-sarif (#32440) Bumps [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) from 4.37.1 to 4.37.2. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/7188fc363630916deb702c7fdcf4e481b751f97a...e0647621c2984b5ed2f768cb892365bf2a616ad1) --- updated-dependencies: - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.37.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 41f97dd45..879ff1325 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1 + uses: github/codeql-action/upload-sarif@e0647621c2984b5ed2f768cb892365bf2a616ad1 # v4.37.2 with: sarif_file: results.sarif From ed246e29f7940fffa4631aa67cfef66c5722d4a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 16:08:39 -0600 Subject: [PATCH 484/571] chore(deps): bump actions/labeler from 6.2.0 to 7.0.0 (#32441) Bumps [actions/labeler](https://github.com/actions/labeler) from 6.2.0 to 7.0.0. - [Release notes](https://github.com/actions/labeler/releases) - [Commits](https://github.com/actions/labeler/compare/b8dd2d9be0f68b860e7dae5dae7d772984eacd6d...bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13) --- updated-dependencies: - dependency-name: actions/labeler dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/label-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label-pr.yml b/.github/workflows/label-pr.yml index 5c2b2fa17..11f0f5d00 100644 --- a/.github/workflows/label-pr.yml +++ b/.github/workflows/label-pr.yml @@ -11,6 +11,6 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # pin@v6.2.0 + - uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # pin@v7.0.0 with: sync-labels: true From fe3f98b5641fe3a0a9fa2575df8698136c2a0029 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 09:11:20 +0000 Subject: [PATCH 485/571] chore(deps): bump google.golang.org/grpc from 1.80.0 to 1.82.1 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.80.0 to 1.82.1. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.80.0...v1.82.1) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.82.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 016b0fded..dffe65d46 100644 --- a/go.mod +++ b/go.mod @@ -165,9 +165,9 @@ require ( golang.org/x/sys v0.47.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.47.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/grpc v1.80.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect + google.golang.org/grpc v1.82.1 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index d097bc72c..ded47f4be 100644 --- a/go.sum +++ b/go.sum @@ -465,12 +465,12 @@ golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= -google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= +google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From ea7c838e0875449a4b2fe141a961aa29f1435a5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 19:09:19 +0000 Subject: [PATCH 486/571] chore(deps): bump the github-actions group with 4 updates Bumps the github-actions group with 4 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action). Updates `github/codeql-action/init` from 4.37.2 to 4.37.3 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/e0647621c2984b5ed2f768cb892365bf2a616ad1...e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81) Updates `github/codeql-action/autobuild` from 4.37.2 to 4.37.3 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/e0647621c2984b5ed2f768cb892365bf2a616ad1...e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81) Updates `github/codeql-action/analyze` from 4.37.2 to 4.37.3 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/e0647621c2984b5ed2f768cb892365bf2a616ad1...e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81) Updates `github/codeql-action/upload-sarif` from 4.37.2 to 4.37.3 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/e0647621c2984b5ed2f768cb892365bf2a616ad1...e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.37.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/analyze dependency-version: 4.37.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.37.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b85756718..d8c1a9d12 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@e0647621c2984b5ed2f768cb892365bf2a616ad1 # pinv4.37.2 + uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # pinv4.37.3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@e0647621c2984b5ed2f768cb892365bf2a616ad1 # pinv4.37.2 + uses: github/codeql-action/autobuild@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # pinv4.37.3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e0647621c2984b5ed2f768cb892365bf2a616ad1 # pinv4.37.2 + uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # pinv4.37.3 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 879ff1325..d8f878f10 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@e0647621c2984b5ed2f768cb892365bf2a616ad1 # v4.37.2 + uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3 with: sarif_file: results.sarif From 050d3e99b9dc8796ced4b99cb272cba91c43055f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 04:32:02 -0600 Subject: [PATCH 487/571] chore(deps): bump ossf/scorecard-action in the github-actions group (#32458) Bumps the github-actions group with 1 update: [ossf/scorecard-action](https://github.com/ossf/scorecard-action). Updates `ossf/scorecard-action` from 2.4.3 to 2.4.4 - [Release notes](https://github.com/ossf/scorecard-action/releases) - [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md) - [Commits](https://github.com/ossf/scorecard-action/compare/4eaacf0543bb3f2c246792bd56e8cdeffafb205a...2d1146689b8cda280b9bc96326124645441f03bc) --- updated-dependencies: - dependency-name: ossf/scorecard-action dependency-version: 2.4.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index d8f878f10..6c142b511 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -33,7 +33,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 with: results_file: results.sarif results_format: sarif From 8f74dce6ba8d7264ce75f52507616720865e712d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 04:40:32 -0600 Subject: [PATCH 488/571] chore(deps): bump the k8s-io group across 1 directory with 7 updates (#32459) Bumps the k8s-io group with 4 updates in the / directory: [k8s.io/api](https://github.com/kubernetes/api), [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver), [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime) and [k8s.io/kubectl](https://github.com/kubernetes/kubectl). Updates `k8s.io/api` from 0.36.2 to 0.36.3 - [Commits](https://github.com/kubernetes/api/compare/v0.36.2...v0.36.3) Updates `k8s.io/apiextensions-apiserver` from 0.36.2 to 0.36.3 - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.36.2...v0.36.3) Updates `k8s.io/apimachinery` from 0.36.2 to 0.36.3 - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.36.2...v0.36.3) Updates `k8s.io/apiserver` from 0.36.2 to 0.36.3 - [Commits](https://github.com/kubernetes/apiserver/compare/v0.36.2...v0.36.3) Updates `k8s.io/cli-runtime` from 0.36.2 to 0.36.3 - [Commits](https://github.com/kubernetes/cli-runtime/compare/v0.36.2...v0.36.3) Updates `k8s.io/client-go` from 0.36.2 to 0.36.3 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.36.2...v0.36.3) Updates `k8s.io/kubectl` from 0.36.2 to 0.36.3 - [Commits](https://github.com/kubernetes/kubectl/compare/v0.36.2...v0.36.3) --- updated-dependencies: - dependency-name: k8s.io/api dependency-version: 0.36.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apiextensions-apiserver dependency-version: 0.36.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apimachinery dependency-version: 0.36.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apiserver dependency-version: 0.36.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/cli-runtime dependency-version: 0.36.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/client-go dependency-version: 0.36.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/kubectl dependency-version: 0.36.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 18 +++++++++--------- go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index dffe65d46..6b9878bb2 100644 --- a/go.mod +++ b/go.mod @@ -39,14 +39,14 @@ require ( golang.org/x/term v0.45.0 golang.org/x/text v0.40.0 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.36.2 - k8s.io/apiextensions-apiserver v0.36.2 - k8s.io/apimachinery v0.36.2 - k8s.io/apiserver v0.36.2 - k8s.io/cli-runtime v0.36.2 - k8s.io/client-go v0.36.2 + k8s.io/api v0.36.3 + k8s.io/apiextensions-apiserver v0.36.3 + k8s.io/apimachinery v0.36.3 + k8s.io/apiserver v0.36.3 + k8s.io/cli-runtime v0.36.3 + k8s.io/client-go v0.36.3 k8s.io/klog/v2 v2.140.0 - k8s.io/kubectl v0.36.2 + k8s.io/kubectl v0.36.3 oras.land/oras-go/v2 v2.6.2 sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/kustomize/kyaml v0.21.1 @@ -172,11 +172,11 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/component-base v0.36.2 // indirect + k8s.io/component-base v0.36.3 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.21.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.3 // indirect ) diff --git a/go.sum b/go.sum index ded47f4be..e5933d99a 100644 --- a/go.sum +++ b/go.sum @@ -487,26 +487,26 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY= -k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg= -k8s.io/apiextensions-apiserver v0.36.2 h1:3O5gqOj/dt2XWWbpMe+TXWpE9yU6pjM/tXxtHHJT/K4= -k8s.io/apiextensions-apiserver v0.36.2/go.mod h1:cL1tBWe8XSaP1H30iWKGo7hf6iAUUUJPEU70dskmAnA= -k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ= -k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4= -k8s.io/apiserver v0.36.2 h1:6vMnkmHZPeBloNkHUhmZYq7Ylv8WIB8xjyEl+eSt26E= -k8s.io/apiserver v0.36.2/go.mod h1:9PoQ2ikCytrZyZg11mGhLEF5m8Rgsb5FJmYJ4Wvnl1k= -k8s.io/cli-runtime v0.36.2 h1:CconTvEeV4DJs4ZX3HQKCFbFRGsm6OtuBM9yjmMP2VM= -k8s.io/cli-runtime v0.36.2/go.mod h1:LddcjiMf4YlnHO7c1Y7rEtDqL84FyiYVLco7V679GUU= -k8s.io/client-go v0.36.2 h1:bfgxmFKc9CgqsgX4xKLAAdmTQlWee7Ob/HlDOrJ5TBI= -k8s.io/client-go v0.36.2/go.mod h1:1vgO4OAlfPnoLcb+Rze2GF5rAr14w8qjrYMoyXJzQj0= -k8s.io/component-base v0.36.2 h1:Z0VH80O7Ng0HDZnZj3WRR3urEGa0kTwmO8CwEwjVK1w= -k8s.io/component-base v0.36.2/go.mod h1:mGfFOA7Gwpdm1VW2cwSQYbiDIlz8GD2WGwH88QSeCyA= +k8s.io/api v0.36.3 h1:NxB+05W2UGqXWFXcLO0RB5cnqnUPP5v5sVlaOH0Iz4w= +k8s.io/api v0.36.3/go.mod h1:JzLQKqRHC5+I8RVj/lS3lCg0mg6nWI9Fo/Sk3ElxHzg= +k8s.io/apiextensions-apiserver v0.36.3 h1:dPmOAPhwTtqb1bTxbFPsy18KHPhktQeO3WUPXunZIB0= +k8s.io/apiextensions-apiserver v0.36.3/go.mod h1:KTXFqgXiuw2pRoL+Wpmttqc+up9Xt/GohadPWeLLOa4= +k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM= +k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE= +k8s.io/apiserver v0.36.3 h1:MGSg2SkdfuytiDEcRylT5mQFmmSsbx90XFUO67Y4bsQ= +k8s.io/apiserver v0.36.3/go.mod h1:fVH7zv9EUNUA7Fl7LtDKh8aB9W7u1VQPSGtWV5SjUxg= +k8s.io/cli-runtime v0.36.3 h1:g+eJ+M1sYpnNYp/q5fzaw2KejIL0Q7DH+xFl6YVoL4U= +k8s.io/cli-runtime v0.36.3/go.mod h1:hZpAqK8nSFXvvLaVCbzUPVp8e9TRLSTCfpNzMt7s3tE= +k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg= +k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30= +k8s.io/component-base v0.36.3 h1:vc/UFvPCkW0irPz84LAodAL1j3f4xktPM6dDJIEheAY= +k8s.io/component-base v0.36.3/go.mod h1:hZbNFG+gCMl9EbykDGEu73feKP9/Cq6JsV4pTo9GTO8= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/kubectl v0.36.2 h1:rpUGGpeL09XVOLep2yle5jrtk//JA1L6ZHfkQQtVEwk= -k8s.io/kubectl v0.36.2/go.mod h1:gVbQ3B/yb4bSR2ggQ7rd0W6icUSWs7sduH4e16Vii+0= +k8s.io/kubectl v0.36.3 h1:TesKp+XYQEjPYoFvuobcVnuvira2+/xAVlq//+kksaI= +k8s.io/kubectl v0.36.3/go.mod h1:W+NEb1CzBGmoaI1Nrpn2ETo9omNBl0AsyxnnMT40N6E= k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc= k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= @@ -521,7 +521,7 @@ sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7 sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.3 h1:u08YRbVUi59ri4YD6cg0UqNM4Dimn0sIl+wldcx5PYw= +sigs.k8s.io/structured-merge-diff/v6 v6.3.3/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= From 5142ca8ddca79a19c8529508c77ab4b27cb23a8f Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 24 Jul 2026 13:24:06 +0200 Subject: [PATCH 489/571] chore(internal): refactor: finer tests conversions to testify part 1 #### Description refactor: finer tests conversions to testify in internal/chart/v3/util Signed-off-by: Matthieu MOREL --- internal/chart/v3/lint/lint_test.go | 15 +++--- internal/chart/v3/lint/rules/crds_test.go | 3 +- internal/chart/v3/lint/rules/template_test.go | 11 ++-- .../chart/v3/lint/support/message_test.go | 6 +-- internal/chart/v3/loader/load_test.go | 31 ++++------- internal/chart/v3/util/chartfile_test.go | 19 +------ internal/chart/v3/util/expand_test.go | 4 +- internal/chart/v3/util/save_test.go | 5 +- .../plugin/installer/http_installer_test.go | 26 +++++----- internal/plugin/installer/installer_test.go | 2 - .../plugin/installer/vcs_installer_test.go | 25 ++++----- internal/plugin/subprocess_commands_test.go | 15 +++--- .../release/v2/util/manifest_sorter_test.go | 1 - internal/resolver/resolver_test.go | 52 ++++++++----------- 14 files changed, 84 insertions(+), 131 deletions(-) diff --git a/internal/chart/v3/lint/lint_test.go b/internal/chart/v3/lint/lint_test.go index 8916c6e22..b0a7ce651 100644 --- a/internal/chart/v3/lint/lint_test.go +++ b/internal/chart/v3/lint/lint_test.go @@ -114,7 +114,7 @@ func TestBadValuesV3(t *testing.T) { func TestBadCrdFileV3(t *testing.T) { var values map[string]any m := RunAll(badCrdFileDir, values, namespace).Messages - assert.Lenf(t, m, 2, "All didn't fail with expected errors, got %#v", m) + require.Lenf(t, m, 2, "All didn't fail with expected errors, got %#v", m) require.ErrorContains(t, m[0].Err, "apiVersion is not in 'apiextensions.k8s.io'") assert.ErrorContains(t, m[1].Err, "object kind is not 'CustomResourceDefinition'") } @@ -122,8 +122,7 @@ func TestBadCrdFileV3(t *testing.T) { func TestGoodChart(t *testing.T) { var values map[string]any m := RunAll(goodChartDir, values, namespace).Messages - if len(m) != 0 { - t.Error("All returned linter messages when it shouldn't have") + if !assert.Empty(t, m, "All returned linter messages when it shouldn't have") { for i, msg := range m { t.Logf("Message %d: %s", i, msg) } @@ -143,13 +142,12 @@ func TestHelmCreateChart(t *testing.T) { // Note: we test with strict=true here, even though others have // strict = false. m := RunAll(createdChart, values, namespace, WithSkipSchemaValidation(true)).Messages - if ll := len(m); ll != 1 { - t.Errorf("All should have had exactly 1 error. Got %d", ll) + if !assert.Len(t, m, 1, "All should have had exactly 1 error") { for i, msg := range m { t.Logf("Message %d: %s", i, msg.Error()) } - } else if msg := m[0].Err.Error(); !strings.Contains(msg, "icon is recommended") { - t.Errorf("Unexpected lint error: %s", msg) + } else { + assert.ErrorContains(t, m[0].Err, "icon is recommended") } } @@ -192,8 +190,7 @@ func TestHelmCreateChart_CheckDeprecatedWarnings(t *testing.T) { func TestSubChartValuesChart(t *testing.T) { var values map[string]any m := RunAll(subChartValuesDir, values, namespace).Messages - if len(m) != 0 { - t.Error("All returned linter messages when it shouldn't have") + if !assert.Empty(t, m, "All returned linter messages when it shouldn't have") { for i, msg := range m { t.Logf("Message %d: %s", i, msg) } diff --git a/internal/chart/v3/lint/rules/crds_test.go b/internal/chart/v3/lint/rules/crds_test.go index 60cb1b5ba..9009c1801 100644 --- a/internal/chart/v3/lint/rules/crds_test.go +++ b/internal/chart/v3/lint/rules/crds_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/internal/chart/v3/lint/support" ) @@ -33,7 +34,7 @@ func TestInvalidCrdsDir(t *testing.T) { Crds(&linter) res := linter.Messages - assert.Len(t, res, 1) + require.Len(t, res, 1) assert.ErrorContains(t, res[0].Err, "not a directory") } diff --git a/internal/chart/v3/lint/rules/template_test.go b/internal/chart/v3/lint/rules/template_test.go index b4fa2d311..3271a5bbe 100644 --- a/internal/chart/v3/lint/rules/template_test.go +++ b/internal/chart/v3/lint/rules/template_test.go @@ -199,12 +199,12 @@ func TestDeprecatedAPIFails(t *testing.T) { linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} Templates(&linter, values, namespace, strict) - if l := len(linter.Messages); l != 1 { + if !assert.Len(t, linter.Messages, 1) { for i, msg := range linter.Messages { t.Logf("Message %d: %s", i, msg) } - t.Fatalf("Expected 1 lint error, got %d", l) } + require.Len(t, linter.Messages, 1, "Expected 1 lint error") var err deprecatedAPIError require.ErrorAs(t, linter.Messages[0].Err, &err, "Expected error to be of type deprecatedAPIError") @@ -252,8 +252,7 @@ func TestStrictTemplateParsingMapError(t *testing.T) { ChartDir: filepath.Join(dir, ch.Metadata.Name), } Templates(linter, ch.Values, namespace, strict) - if len(linter.Messages) != 0 { - t.Errorf("expected zero messages, got %d", len(linter.Messages)) + if !assert.Empty(t, linter.Messages, "expected zero messages") { for i, msg := range linter.Messages { t.Logf("Message %d: %q", i, msg) } @@ -376,12 +375,12 @@ func TestEmptyWithCommentsManifests(t *testing.T) { linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} Templates(&linter, values, namespace, strict) - if l := len(linter.Messages); l > 0 { + if !assert.Empty(t, linter.Messages) { for i, msg := range linter.Messages { t.Logf("Message %d: %s", i, msg) } - t.Fatalf("Expected 0 lint errors, got %d", l) } + require.Empty(t, linter.Messages, "Expected 0 lint errors") } func TestValidateListAnnotations(t *testing.T) { md := &k8sYamlStruct{ diff --git a/internal/chart/v3/lint/support/message_test.go b/internal/chart/v3/lint/support/message_test.go index 4c3ed3a3f..d07cf9ed4 100644 --- a/internal/chart/v3/lint/support/message_test.go +++ b/internal/chart/v3/lint/support/message_test.go @@ -58,11 +58,11 @@ func TestRunLinterRule(t *testing.T) { func TestMessage(t *testing.T) { m := Message{ErrorSev, "Chart.yaml", errors.New("Foo")} - require.EqualErrorf(t, m, "[ERROR] Chart.yaml: Foo", "Unexpected output: %s", m.Error()) + require.EqualError(t, m, "[ERROR] Chart.yaml: Foo") m = Message{WarningSev, "templates/", errors.New("Bar")} - require.EqualErrorf(t, m, "[WARNING] templates/: Bar", "Unexpected output: %s", m.Error()) + require.EqualError(t, m, "[WARNING] templates/: Bar") m = Message{InfoSev, "templates/rc.yaml", errors.New("FooBar")} - assert.EqualErrorf(t, m, "[INFO] templates/rc.yaml: FooBar", "Unexpected output: %s", m.Error()) + assert.EqualError(t, m, "[INFO] templates/rc.yaml: FooBar") } diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index 9378bcdf5..dd5fdc8db 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -25,7 +25,6 @@ import ( "log" "os" "path/filepath" - "reflect" "runtime" "strings" "testing" @@ -201,11 +200,9 @@ icon: https://example.com/64x64.png require.NoError(t, err, "Expected good files to be loaded") assert.Equal(t, "frobnitz", c.Name(), "Expected chart name to be 'frobnitz', got %s", c.Name()) assert.Equal(t, "some values", c.Values["var"], "Expected chart values to be populated with default values") - assert.Len(t, c.Raw, 5, "Expected %d files, got %d", 5, len(c.Raw)) - + assert.Len(t, c.Raw, 5, "Expected 5 files") assert.True(t, bytes.Equal(c.Schema, []byte("type: Values")), "Expected chart schema to be populated with default values") - - assert.Len(t, c.Templates, 2, "Expected number of templates == 2, got %d", len(c.Templates)) + assert.Len(t, c.Templates, 2, "Expected 2 templates") _, err = LoadFiles([]*archive.BufferedFile{}) require.Error(t, err, "Expected err to be non-nil") @@ -417,7 +414,7 @@ foo: t.Run(testName, func(tt *testing.T) { values, err := LoadValues(bytes.NewReader(testCase.data)) require.NoError(tt, err) - assert.Truef(tt, reflect.DeepEqual(values, testCase.expctedValues), "Expected values: %v, got %v", testCase.expctedValues, values) + assert.Equalf(tt, testCase.expctedValues, values, "Expected values: %v, got %v", testCase.expctedValues, values) }) } } @@ -445,16 +442,13 @@ func TestMergeValuesV3(t *testing.T) { } testMap := MergeMaps(flatMap, nestedMap) - equal := reflect.DeepEqual(testMap, nestedMap) - assert.True(t, equal, "Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) + assert.Equal(t, testMap, nestedMap, "Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) testMap = MergeMaps(nestedMap, flatMap) - equal = reflect.DeepEqual(testMap, flatMap) - assert.True(t, equal, "Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) + assert.Equal(t, testMap, flatMap, "Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) testMap = MergeMaps(nestedMap, anotherNestedMap) - equal = reflect.DeepEqual(testMap, anotherNestedMap) - assert.True(t, equal, "Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) + assert.Equal(t, testMap, anotherNestedMap, "Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) testMap = MergeMaps(anotherFlatMap, anotherNestedMap) expectedMap := map[string]any{ @@ -465,26 +459,23 @@ func TestMergeValuesV3(t *testing.T) { "awesome": "stuff", }, } - equal = reflect.DeepEqual(testMap, expectedMap) - assert.True(t, equal, "Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) + assert.Equal(t, expectedMap, testMap, "Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) } func verifyChart(t *testing.T, c *chart.Chart) { t.Helper() require.NotEmpty(t, c.Name(), "No chart metadata found on %v", c) t.Logf("Verifying chart %s", c.Name()) - assert.Len(t, c.Templates, 1, "Expected 1 template, got %d", len(c.Templates)) + assert.Len(t, c.Templates, 1, "Expected 1 template") numfiles := 6 - if len(c.Files) != numfiles { - t.Errorf("Expected %d extra files, got %d", numfiles, len(c.Files)) + if !assert.Len(t, c.Files, numfiles, "Expected %d extra files", numfiles) { for _, n := range c.Files { t.Logf("\t%s", n.Name) } } - if len(c.Dependencies()) != 2 { - t.Errorf("Expected 2 dependencies, got %d (%v)", len(c.Dependencies()), c.Dependencies()) + if !assert.Len(t, c.Dependencies(), 2, "Expected 2 dependencies") { for _, d := range c.Dependencies() { t.Logf("\tSubchart: %s\n", d.Name()) } @@ -509,7 +500,7 @@ func verifyChart(t *testing.T, c *chart.Chart) { func verifyDependencies(t *testing.T, c *chart.Chart) { t.Helper() - assert.Len(t, c.Metadata.Dependencies, 2, "Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) + assert.Len(t, c.Metadata.Dependencies, 2, "Expected 2 dependencies") tests := []*chart.Dependency{ {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, diff --git a/internal/chart/v3/util/chartfile_test.go b/internal/chart/v3/util/chartfile_test.go index 0cc8a66af..850f39af2 100644 --- a/internal/chart/v3/util/chartfile_test.go +++ b/internal/chart/v3/util/chartfile_test.go @@ -36,36 +36,21 @@ func TestLoadChartfile(t *testing.T) { func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { t.Helper() require.NotNil(t, f, "Failed verifyChartfile because f is nil") - assert.Equal(t, name, f.Name, "Expected %s, got %s", name, f.Name) - assert.Equal(t, "This is a frobnitz.", f.Description, "Unexpected description %q", f.Description) - assert.Equal(t, "1.2.3", f.Version, "Unexpected version %q", f.Version) - assert.Len(t, f.Maintainers, 2, "Expected 2 maintainers, got %d", len(f.Maintainers)) - assert.Equal(t, "The Helm Team", f.Maintainers[0].Name, "Unexpected maintainer name.") - assert.Equal(t, "nobody@example.com", f.Maintainers[1].Email, "Unexpected maintainer email.") - require.Len(t, f.Sources, 1, "Unexpected number of sources") - assert.Equal(t, "https://example.com/foo/bar", f.Sources[0], "Expected https://example.com/foo/bar, got %s", f.Sources) - assert.Equal(t, "http://example.com", f.Home, "Unexpected home.") - assert.Equal(t, "https://example.com/64x64.png", f.Icon, "Unexpected icon: %q", f.Icon) - require.Len(t, f.Keywords, 3, "Unexpected keywords") - require.Len(t, f.Annotations, 2, "Unexpected annotations") - want, got := "extravalue", f.Annotations["extrakey"] - assert.Equalf(t, want, got, "Want %q, but got %q", want, got) - - want, got = "anothervalue", f.Annotations["anotherkey"] - assert.Equalf(t, want, got, "Want %q, but got %q", want, got) + assert.Equal(t, "extravalue", f.Annotations["extrakey"]) + assert.Equal(t, "anothervalue", f.Annotations["anotherkey"]) kk := []string{"frobnitz", "sprocket", "dodad"} for i, k := range f.Keywords { diff --git a/internal/chart/v3/util/expand_test.go b/internal/chart/v3/util/expand_test.go index e6d66b74e..ddf74b392 100644 --- a/internal/chart/v3/util/expand_test.go +++ b/internal/chart/v3/util/expand_test.go @@ -101,7 +101,7 @@ func TestExpand(t *testing.T) { require.NoError(t, err) expectLen := 11 - assert.Len(t, fis, expectLen, "Expected %d files, but got %d", expectLen, len(fis)) + assert.Len(t, fis, expectLen, "Expected %d files", expectLen) for _, fi := range fis { expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name())) @@ -154,7 +154,7 @@ func TestExpandFile(t *testing.T) { require.NoError(t, err) expectLen := 11 - assert.Len(t, fis, expectLen, "Expected %d files, but got %d", expectLen, len(fis)) + assert.Len(t, fis, expectLen, "Expected %d files", expectLen) for _, fi := range fis { expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name())) diff --git a/internal/chart/v3/util/save_test.go b/internal/chart/v3/util/save_test.go index f7bd5dcfb..26bb54019 100644 --- a/internal/chart/v3/util/save_test.go +++ b/internal/chart/v3/util/save_test.go @@ -69,9 +69,8 @@ func TestSave(t *testing.T) { c2, err := loader.LoadFile(where) require.NoError(t, err) require.Equal(t, c.Name(), c2.Name(), "Expected chart archive to have %q, got %q", c.Name(), c2.Name()) - if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" { - t.Fatal("Files data did not match") - } + require.Len(t, c2.Files, 1, "Files data did not match") + require.Equal(t, "scheherazade/shahryar.txt", c2.Files[0].Name, "Files data did not match") if !bytes.Equal(c.Schema, c2.Schema) { indentation := 4 diff --git a/internal/plugin/installer/http_installer_test.go b/internal/plugin/installer/http_installer_test.go index d0d5e099d..df8385ed6 100644 --- a/internal/plugin/installer/http_installer_test.go +++ b/internal/plugin/installer/http_installer_test.go @@ -67,10 +67,10 @@ func mockArchiveServer() *httptest.Server { if !strings.HasSuffix(r.URL.Path, ".tar.gz") { w.Header().Add("Content-Type", "text/html") fmt.Fprintln(w, "broken") - return + } else { + w.Header().Add("Content-Type", "application/gzip") + fmt.Fprintln(w, "test") } - w.Header().Add("Content-Type", "application/gzip") - fmt.Fprintln(w, "test") })) } @@ -221,22 +221,22 @@ func TestExtract(t *testing.T) { expectedReadmePerm := os.FileMode(0o777 &^ currentUmask) pluginYAMLFullPath := filepath.Join(tempDir, "plugin.yaml") - if info, err := os.Stat(pluginYAMLFullPath); err != nil { + info, err := os.Stat(pluginYAMLFullPath) + if err != nil { require.NotErrorIs(t, err, fs.ErrNotExist, "Expected %s to exist but doesn't", pluginYAMLFullPath) - t.Fatal(err) - } else { - require.Equalf(t, expectedPluginYAMLPerm, info.Mode().Perm(), "Expected %s to have %o mode but has %o (umask: %o)", - pluginYAMLFullPath, expectedPluginYAMLPerm, info.Mode().Perm(), currentUmask) } + require.NoError(t, err) + require.Equalf(t, expectedPluginYAMLPerm, info.Mode().Perm(), "Expected %s to have %o mode but has %o (umask: %o)", + pluginYAMLFullPath, expectedPluginYAMLPerm, info.Mode().Perm(), currentUmask) readmeFullPath := filepath.Join(tempDir, "README.md") - if info, err := os.Stat(readmeFullPath); err != nil { + info, err = os.Stat(readmeFullPath) + if err != nil { require.NotErrorIs(t, err, fs.ErrNotExist, "Expected %s to exist but doesn't", readmeFullPath) - t.Fatal(err) - } else { - require.Equalf(t, expectedReadmePerm, info.Mode().Perm(), "Expected %s to have %o mode but has %o (umask: %o)", - readmeFullPath, expectedReadmePerm, info.Mode().Perm(), currentUmask) } + require.NoError(t, err) + require.Equalf(t, expectedReadmePerm, info.Mode().Perm(), "Expected %s to have %o mode but has %o (umask: %o)", + readmeFullPath, expectedReadmePerm, info.Mode().Perm(), currentUmask) } func TestCleanJoin(t *testing.T) { diff --git a/internal/plugin/installer/installer_test.go b/internal/plugin/installer/installer_test.go index 620afc183..75c22787b 100644 --- a/internal/plugin/installer/installer_test.go +++ b/internal/plugin/installer/installer_test.go @@ -34,8 +34,6 @@ func TestIsRemoteHTTPArchive(t *testing.T) { // Test with invalid extension and unreachable server assert.False(t, isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.notanarchive"), "Bad URL without valid extension should not succeed") - assert.True(t, isRemoteHTTPArchive(source), "Expected %q to be a valid archive URL", source) - assert.False(t, isRemoteHTTPArchive(source+"-not-an-extension"), "Expected media type match to fail") } diff --git a/internal/plugin/installer/vcs_installer_test.go b/internal/plugin/installer/vcs_installer_test.go index 3f5b098e4..82401d069 100644 --- a/internal/plugin/installer/vcs_installer_test.go +++ b/internal/plugin/installer/vcs_installer_test.go @@ -96,16 +96,14 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) { require.NoError(t, err) // ensure a VCSInstaller was returned - _, ok := i.(*VCSInstaller) - require.True(t, ok, "expected a VCSInstaller") + require.IsType(t, &VCSInstaller{}, i, "expected a VCSInstaller") - if err := Install(i); err == nil { - t.Fatal("expected error for version does not exists, got none") - } else if strings.Contains(err.Error(), "Could not resolve host: github.com") { + err = Install(i) + require.Error(t, err, "expected error for version does not exists, got none") + if strings.Contains(err.Error(), "Could not resolve host: github.com") { t.Skip("Unable to run test without Internet access") - } else { - require.EqualErrorf(t, err, fmt.Sprintf("requested version %q does not exist for plugin %q", version, source), "expected error for version does not exists") } + require.EqualErrorf(t, err, fmt.Sprintf("requested version %q does not exist for plugin %q", version, source), "expected error for version does not exists") } func TestVCSInstallerUpdate(t *testing.T) { ensure.HelmHome(t) @@ -116,19 +114,16 @@ func TestVCSInstallerUpdate(t *testing.T) { require.NoError(t, err) // ensure a VCSInstaller was returned - _, ok := i.(*VCSInstaller) - require.True(t, ok, "expected a VCSInstaller") + require.IsType(t, &VCSInstaller{}, i, "expected a VCSInstaller") require.EqualErrorf(t, Update(i), "plugin does not exist", "expected error for plugin does not exist") // Install plugin before update - if err := Install(i); err != nil { - if strings.Contains(err.Error(), "Could not resolve host: github.com") { - t.Skip("Unable to run test without Internet access") - } else { - t.Fatal(err) - } + err = Install(i) + if err != nil && strings.Contains(err.Error(), "Could not resolve host: github.com") { + t.Skip("Unable to run test without Internet access") } + require.NoError(t, err) // Test FindSource method for positive result pluginInfo, err := FindSource(i.Path()) diff --git a/internal/plugin/subprocess_commands_test.go b/internal/plugin/subprocess_commands_test.go index b2ac19c02..7505bf343 100644 --- a/internal/plugin/subprocess_commands_test.go +++ b/internal/plugin/subprocess_commands_test.go @@ -16,7 +16,6 @@ limitations under the License. package plugin import ( - "reflect" "runtime" "testing" @@ -39,7 +38,7 @@ func TestPrepareCommand(t *testing.T) { cmd, args, err := PrepareCommands(platformCommand, true, []string{}, env) require.NoError(t, err) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) - require.Truef(t, reflect.DeepEqual(args, cmdArgs), "Expected %v, got %v", cmdArgs, args) + require.Equalf(t, args, cmdArgs, "Expected %v, got %v", cmdArgs, args) } func TestPrepareCommandExtraArgs(t *testing.T) { @@ -111,7 +110,7 @@ func TestPrepareCommands(t *testing.T) { cmd, args, err := PrepareCommands(cmds, true, []string{}, env) require.NoError(t, err) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) - require.Truef(t, reflect.DeepEqual(args, cmdArgs), "Expected %v, got %v", cmdArgs, args) + require.Equalf(t, args, cmdArgs, "Expected %v, got %v", cmdArgs, args) } func TestPrepareCommandsExtraArgs(t *testing.T) { @@ -132,7 +131,7 @@ func TestPrepareCommandsExtraArgs(t *testing.T) { cmd, args, err := PrepareCommands(cmds, true, extraArgs, env) require.NoError(t, err) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) - require.Truef(t, reflect.DeepEqual(args, expectedArgs), "Expected %v, got %v", expectedArgs, args) + require.Equalf(t, expectedArgs, args, "Expected %v, got %v", expectedArgs, args) } func TestPrepareCommandsNoArch(t *testing.T) { @@ -149,7 +148,7 @@ func TestPrepareCommandsNoArch(t *testing.T) { cmd, args, err := PrepareCommands(cmds, true, []string{}, env) require.NoError(t, err) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) - require.Truef(t, reflect.DeepEqual(args, cmdArgs), "Expected %v, got %v", cmdArgs, args) + require.Equalf(t, args, cmdArgs, "Expected %v, got %v", cmdArgs, args) } func TestPrepareCommandsNoOsNoArch(t *testing.T) { @@ -166,7 +165,7 @@ func TestPrepareCommandsNoOsNoArch(t *testing.T) { cmd, args, err := PrepareCommands(cmds, true, []string{}, env) require.NoError(t, err) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) - require.Truef(t, reflect.DeepEqual(args, cmdArgs), "Expected %v, got %v", cmdArgs, args) + require.Equalf(t, args, cmdArgs, "Expected %v, got %v", cmdArgs, args) } func TestPrepareCommandsNoMatch(t *testing.T) { @@ -206,7 +205,7 @@ func TestPrepareCommandsExpand(t *testing.T) { cmd, args, err := PrepareCommands(cmds, true, []string{}, env) require.NoError(t, err) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) - require.Truef(t, reflect.DeepEqual(args, expectedArgs), "Expected %v, got %v", expectedArgs, args) + require.Equalf(t, expectedArgs, args, "Expected %v, got %v", expectedArgs, args) } func TestPrepareCommandsNoExpand(t *testing.T) { @@ -223,5 +222,5 @@ func TestPrepareCommandsNoExpand(t *testing.T) { cmd, args, err := PrepareCommands(cmds, false, []string{}, env) require.NoError(t, err) require.Equal(t, cmdMain, cmd, "Expected %q, got %q", cmdMain, cmd) - require.Truef(t, reflect.DeepEqual(args, cmdArgs), "Expected %v, got %v", cmdArgs, args) + require.Equalf(t, args, cmdArgs, "Expected %v, got %v", cmdArgs, args) } diff --git a/internal/release/v2/util/manifest_sorter_test.go b/internal/release/v2/util/manifest_sorter_test.go index deb9426ce..6cf529bff 100644 --- a/internal/release/v2/util/manifest_sorter_test.go +++ b/internal/release/v2/util/manifest_sorter_test.go @@ -143,7 +143,6 @@ metadata: // This test will fail if 'six' or 'seven' was added. assert.Len(t, generic, 2) - assert.Len(t, hs, 4) for _, out := range hs { diff --git a/internal/resolver/resolver_test.go b/internal/resolver/resolver_test.go index 547419e17..db976a45b 100644 --- a/internal/resolver/resolver_test.go +++ b/internal/resolver/resolver_test.go @@ -148,28 +148,22 @@ func TestResolve(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { l, err := r.Resolve(tt.req, repoNames) - if err != nil { - if tt.err { - return - } + if tt.err { + require.Error(t, err) + } else { require.NoError(t, err) - } + h, err := HashReq(tt.req, tt.expect.Dependencies) + require.NoError(t, err) + assert.Equal(t, l.Digest, h, "%q: hashes don't match.", tt.name) - if tt.err { - require.Failf(t, "Expected error in test %q", tt.name) + // Check fields. + require.Len(t, l.Dependencies, len(tt.req), "%s: wrong number of dependencies in lock", tt.name) + d0 := l.Dependencies[0] + e0 := tt.expect.Dependencies[0] + assert.Equal(t, e0.Name, d0.Name, tt.name) + assert.Equal(t, e0.Repository, d0.Repository, tt.name) + assert.Equal(t, e0.Version, d0.Version, tt.name) } - - h, err := HashReq(tt.req, tt.expect.Dependencies) - require.NoError(t, err) - assert.Equal(t, l.Digest, h, "%q: hashes don't match.", tt.name) - - // Check fields. - assert.Len(t, l.Dependencies, len(tt.req), "%s: wrong number of dependencies in lock", tt.name) - d0 := l.Dependencies[0] - e0 := tt.expect.Dependencies[0] - assert.Equal(t, e0.Name, d0.Name, tt.name) - assert.Equal(t, e0.Repository, d0.Repository, tt.name) - assert.Equal(t, e0.Version, d0.Version, tt.name) }) } } @@ -280,20 +274,16 @@ func TestGetLocalPath(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p, err := GetLocalPath(tt.repo, tt.chartpath) - if err != nil { - if tt.err { - return - } - require.NoError(t, err) - } if tt.err { - require.Failf(t, "Expected error in test %q", tt.name) - } - expect := tt.expect - if runtime.GOOS == "windows" { - expect = tt.winExpect + require.Error(t, err) + } else { + require.NoError(t, err) + if runtime.GOOS == "windows" { + assert.Equal(t, tt.winExpect, p, tt.name) + } else { + assert.Equal(t, tt.expect, p, tt.name) + } } - assert.Equal(t, expect, p, tt.name) }) } } From 4d9c03f51c30f851a03676a1315f492de10210e6 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 24 Jul 2026 13:24:06 +0200 Subject: [PATCH 490/571] chore(pkg): refactor: finer tests conversions to testify part 2 #### Description refactor: finer tests conversions to testify in pkg/storage/driver Signed-off-by: Matthieu MOREL --- pkg/chart/v2/lint/lint_test.go | 15 +++++------ pkg/chart/v2/util/validate_name_test.go | 28 +++++++++++---------- pkg/cli/values/options_test.go | 30 +++++++++++----------- pkg/cmd/package_test.go | 30 +++++++++++----------- pkg/cmd/pull_test.go | 30 +++++++++++----------- pkg/cmd/search/search_test.go | 33 +++++++++++-------------- pkg/cmd/upgrade_test.go | 19 +++++--------- pkg/downloader/manager_test.go | 23 +++++++---------- pkg/engine/engine_test.go | 27 ++++++++------------ pkg/getter/ocigetter_test.go | 10 -------- pkg/repo/v1/index_test.go | 24 +++++++----------- pkg/storage/driver/cfgmaps_test.go | 23 ++++++++--------- pkg/storage/driver/memory_test.go | 11 +++------ pkg/storage/driver/secrets_test.go | 21 +++++++--------- pkg/storage/driver/sql_test.go | 25 ++++++++----------- pkg/storage/storage_test.go | 15 ++++++----- 16 files changed, 151 insertions(+), 213 deletions(-) diff --git a/pkg/chart/v2/lint/lint_test.go b/pkg/chart/v2/lint/lint_test.go index 1782c01d4..c4705872d 100644 --- a/pkg/chart/v2/lint/lint_test.go +++ b/pkg/chart/v2/lint/lint_test.go @@ -118,7 +118,7 @@ func TestBadValues(t *testing.T) { func TestBadCrdFile(t *testing.T) { var values map[string]any m := RunAll(badCrdFileDir, values, namespace).Messages - assert.Lenf(t, m, 2, "All didn't fail with expected errors, got %#v", m) + require.Lenf(t, m, 2, "All didn't fail with expected errors, got %#v", m) require.ErrorContains(t, m[0].Err, "apiVersion is not in 'apiextensions.k8s.io'") assert.ErrorContains(t, m[1].Err, "object kind is not 'CustomResourceDefinition'") } @@ -126,8 +126,7 @@ func TestBadCrdFile(t *testing.T) { func TestGoodChart(t *testing.T) { var values map[string]any m := RunAll(goodChartDir, values, namespace).Messages - if len(m) != 0 { - t.Error("All returned linter messages when it shouldn't have") + if !assert.Empty(t, m, "All returned linter messages when it shouldn't have") { for i, msg := range m { t.Logf("Message %d: %s", i, msg) } @@ -147,13 +146,12 @@ func TestHelmCreateChart(t *testing.T) { // Note: we test with strict=true here, even though others have // strict = false. m := RunAll(createdChart, values, namespace, WithSkipSchemaValidation(true)).Messages - if ll := len(m); ll != 1 { - t.Errorf("All should have had exactly 1 error. Got %d", ll) + if !assert.Len(t, m, 1, "All should have had exactly 1 error") { for i, msg := range m { t.Logf("Message %d: %s", i, msg.Error()) } - } else if msg := m[0].Err.Error(); !strings.Contains(msg, "icon is recommended") { - t.Errorf("Unexpected lint error: %s", msg) + } else { + assert.ErrorContains(t, m[0].Err, "icon is recommended") } } @@ -196,8 +194,7 @@ func TestHelmCreateChart_CheckDeprecatedWarnings(t *testing.T) { func TestSubChartValuesChart(t *testing.T) { var values map[string]any m := RunAll(subChartValuesDir, values, namespace).Messages - if len(m) != 0 { - t.Error("All returned linter messages when it shouldn't have") + if !assert.Empty(t, m, "All returned linter messages when it shouldn't have") { for i, msg := range m { t.Logf("Message %d: %s", i, msg) } diff --git a/pkg/chart/v2/util/validate_name_test.go b/pkg/chart/v2/util/validate_name_test.go index cfc62a0f7..9095dc4dc 100644 --- a/pkg/chart/v2/util/validate_name_test.go +++ b/pkg/chart/v2/util/validate_name_test.go @@ -16,7 +16,11 @@ limitations under the License. package util -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" +) // TestValidateReleaseName is a regression test for ValidateName // @@ -46,12 +50,11 @@ func TestValidateReleaseName(t *testing.T) { "a1111111111111111111111111111111111111111111111111111111111z": false, } for input, expectPass := range names { - if err := ValidateReleaseName(input); (err == nil) != expectPass { - st := "fail" - if expectPass { - st = "succeed" - } - t.Errorf("Expected %q to %s", input, st) + err := ValidateReleaseName(input) + if expectPass { + assert.NoError(t, err) + } else { + assert.Error(t, err) } } } @@ -80,12 +83,11 @@ func TestValidateMetadataName(t *testing.T) { "a1111111111111111111111111111111111111111111111111111111111z": false, } for input, expectPass := range names { - if err := ValidateMetadataName(input); (err == nil) != expectPass { - st := "fail" - if expectPass { - st = "succeed" - } - t.Errorf("Expected %q to %s", input, st) + err := ValidateMetadataName(input) + if expectPass { + assert.NoError(t, err) + } else { + assert.Error(t, err) } } } diff --git a/pkg/cli/values/options_test.go b/pkg/cli/values/options_test.go index 40590f33f..6debb693e 100644 --- a/pkg/cli/values/options_test.go +++ b/pkg/cli/values/options_test.go @@ -215,19 +215,17 @@ func TestReadFile(t *testing.T) { got, err := readFile(actualFilePath, tt.providers) require.NoError(t, err, "readFile() expected no error for stdin") assert.Equal(t, testData, got) - return - } - - // Regular test cases - got, err := readFile(actualFilePath, tt.providers) - if tt.expectError { - assert.Error(t, err) - return - } - require.NoError(t, err) - - if tt.expectedData != nil { - assert.Equal(t, tt.expectedData, got) + } else { + // Regular test cases + got, err := readFile(actualFilePath, tt.providers) + if tt.expectError { + assert.Error(t, err) + } else { + require.NoError(t, err) + if tt.expectedData != nil { + assert.Equal(t, tt.expectedData, got) + } + } } }) } @@ -356,10 +354,10 @@ func TestMergeValuesCLI(t *testing.T) { got, err := tt.opts.MergeValues(getter.Providers{}) if tt.wantErr { assert.Error(t, err) - return + } else { + require.NoError(t, err) + assert.Equal(t, tt.expected, got) } - require.NoError(t, err) - assert.Equal(t, tt.expected, got) }) } } diff --git a/pkg/cmd/package_test.go b/pkg/cmd/package_test.go index d78f5d88d..8b19faf08 100644 --- a/pkg/cmd/package_test.go +++ b/pkg/cmd/package_test.go @@ -139,24 +139,22 @@ func TestPackage(t *testing.T) { } } _, _, err = executeActionCommand(strings.Join(cmd, " ")) - - if err != nil { - if tt.err && re.MatchString(err.Error()) { - return + if tt.err { + require.Error(t, err) + require.True(t, re.MatchString(err.Error())) + } else { + require.NoError(t, err) + if tt.hasfile != "" { + fi, err := os.Stat(tt.hasfile) + require.NoErrorf(t, err, "%q: expected file %q", tt.name, tt.hasfile) + assert.NotEqualf(t, 0, fi.Size(), "%q: file %q has zero bytes.", tt.name, tt.hasfile) } - t.Fatalf("%q: expected error %q, got %q", tt.name, tt.expect, err) - } - if tt.hasfile != "" { - fi, err := os.Stat(tt.hasfile) - require.NoErrorf(t, err, "%q: expected file %q", tt.name, tt.hasfile) - assert.NotEqualf(t, 0, fi.Size(), "%q: file %q has zero bytes.", tt.name, tt.hasfile) - } - - if v, ok := tt.flags["sign"]; ok && v == "1" { - fi, err := os.Stat(tt.hasfile + ".prov") - require.NoErrorf(t, err, "%q: expected provenance file", tt.name) - assert.NotEqualf(t, 0, fi.Size(), "%q: provenance file is empty", tt.name) + if v, ok := tt.flags["sign"]; ok && v == "1" { + fi, err := os.Stat(tt.hasfile + ".prov") + require.NoErrorf(t, err, "%q: expected provenance file", tt.name) + assert.NotEqualf(t, 0, fi.Size(), "%q: provenance file is empty", tt.name) + } } }) } diff --git a/pkg/cmd/pull_test.go b/pkg/cmd/pull_test.go index 352c36a29..d0371710b 100644 --- a/pkg/cmd/pull_test.go +++ b/pkg/cmd/pull_test.go @@ -237,25 +237,23 @@ func TestPullCmd(t *testing.T) { require.NoError(t, os.MkdirAll(file, 0o755)) } _, out, err := executeActionCommand(cmd) - if err != nil { - if tt.wantError { - if tt.wantErrorMsg != "" && tt.wantErrorMsg != err.Error() { - t.Fatalf("Actual error '%s', not equal to expected error '%s'", err, tt.wantErrorMsg) - } - return + if tt.wantError { + if tt.wantErrorMsg != "" { + require.EqualError(t, err, tt.wantErrorMsg, "Actual error '%s', not equal to expected error '%s'", err, tt.wantErrorMsg) } - t.Fatalf("%q reported error: %s", tt.name, err) - } + } else { + require.NoError(t, err) - if tt.expectVerify { - outString := helmTestKeyOut + tt.expectSha + "\n" - assert.Equal(t, outString, out, "%q: expected verification output %q, got %q", tt.name, outString, out) - } + if tt.expectVerify { + outString := helmTestKeyOut + tt.expectSha + "\n" + assert.Equal(t, outString, out, "%q: expected verification output %q, got %q", tt.name, outString, out) + } - ef := filepath.Join(outdir, tt.expectFile) - fi, err := os.Stat(ef) - require.NoError(t, err, "%q: expected a file at %s.", tt.name, ef) - assert.Equal(t, tt.expectDir, fi.IsDir(), "%q: expected directory=%t, but it's not.", tt.name, tt.expectDir) + ef := filepath.Join(outdir, tt.expectFile) + fi, err := os.Stat(ef) + require.NoError(t, err, "%q: expected a file at %s.", tt.name, ef) + assert.Equal(t, tt.expectDir, fi.IsDir(), "%q: expected directory=%t, but it's not.", tt.name, tt.expectDir) + } }) } } diff --git a/pkg/cmd/search/search_test.go b/pkg/cmd/search/search_test.go index f8c9504d1..e45386947 100644 --- a/pkg/cmd/search/search_test.go +++ b/pkg/cmd/search/search_test.go @@ -232,26 +232,21 @@ func TestSearchByName(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { charts, err := i.Search(tt.query, 100, tt.regexp) - if err != nil { - if tt.fail { - require.ErrorContains(t, err, tt.failMsg) - return - } - t.Fatalf("%s: %s", tt.name, err) - } - // Give us predictably ordered results. - SortScore(charts) + if tt.fail { + require.ErrorContains(t, err, tt.failMsg) + } else { + require.NoError(t, err) // Give us predictably ordered results. + SortScore(charts) - l := len(charts) - require.Lenf(t, tt.expect, l, "Expected %d result, got %d", len(tt.expect), l) - // For empty result sets, just keep going. - if l == 0 { - return - } - - for i, got := range charts { - ex := tt.expect[i] - assert.Equalf(t, got.Name, ex.Name, "[%d]: Expected name %q, got %q", i, ex.Name, got.Name) + l := len(tt.expect) + require.Len(t, charts, len(tt.expect)) + // For empty result sets, just keep going. + if l != 0 { + for i, got := range charts { + ex := tt.expect[i] + assert.Equalf(t, got.Name, ex.Name, "[%d]: Expected name %q, got %q", i, ex.Name, got.Name) + } + } } }) } diff --git a/pkg/cmd/upgrade_test.go b/pkg/cmd/upgrade_test.go index 1c1823199..5aba7b11b 100644 --- a/pkg/cmd/upgrade_test.go +++ b/pkg/cmd/upgrade_test.go @@ -22,7 +22,6 @@ import ( "net/http/httptest" "os" "path/filepath" - "reflect" "testing" "time" @@ -201,9 +200,9 @@ func TestUpgradeDependencyUpdateOCINoPanic(t *testing.T) { w.Header().Set("Docker-Distribution-API-Version", "registry/2.0") if r.URL.Path == "/v2/" { w.WriteHeader(http.StatusOK) - return + } else { + w.WriteHeader(http.StatusNotFound) } - w.WriteHeader(http.StatusNotFound) })) defer srv.Close() @@ -224,23 +223,17 @@ func TestUpgradeDependencyUpdateOCINoPanic(t *testing.T) { }}, }, } - if err := chartutil.SaveDir(parent, tmp); err != nil { - t.Fatalf("Error creating chart: %v", err) - } + require.NoError(t, chartutil.SaveDir(parent, tmp), "Error creating chart") chartPath := filepath.Join(tmp, parent.Metadata.Name) // SaveDir writes only resolved subcharts (Chart.Dependencies()), not the // declared Metadata.Dependencies, so create the empty charts/ directory // explicitly to make the "dependency missing from charts/" state concrete. - if err := os.MkdirAll(filepath.Join(chartPath, "charts"), 0o755); err != nil { - t.Fatalf("Error creating charts dir: %v", err) - } + require.NoError(t, os.MkdirAll(filepath.Join(chartPath, "charts"), 0o755), "Error creating charts dir") // The command must return an error (registry rejects the lookup), not panic. _, _, err := executeActionCommandC(storageFixture(), fmt.Sprintf("upgrade --dependency-update --plain-http oci-parent '%s'", chartPath)) - if err == nil { - t.Fatal("expected an error resolving the OCI dependency, got nil") - } + require.Error(t, err, "expected an error resolving the OCI dependency, got nil") } func TestUpgradeWithValue(t *testing.T) { @@ -471,7 +464,7 @@ func TestUpgradeInstallWithLabels(t *testing.T) { updatedRel, err := releaserToV1Release(updatedReli) require.NoError(t, err) - assert.Truef(t, reflect.DeepEqual(updatedRel.Labels, expectedLabels), "Expected {%v}, got {%v}", expectedLabels, updatedRel.Labels) + assert.Equal(t, expectedLabels, updatedRel.Labels) } func prepareMockReleaseWithSecret(t *testing.T, releaseName string) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) { diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index 00cb5b668..e40bbbac1 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -20,7 +20,6 @@ import ( "io/fs" "os" "path/filepath" - "reflect" "testing" "time" @@ -168,19 +167,16 @@ func TestGetRepoNames(t *testing.T) { } for _, tt := range tests { - l, err := m.resolveRepoNames(tt.req) - if err != nil { + t.Run(tt.name, func(t *testing.T) { + l, err := m.resolveRepoNames(tt.req) if tt.err { - continue + require.Error(t, err) + } else { + require.NoError(t, err) + // m1 and m2 are the maps we want to compare + assert.Equal(t, l, tt.expect, "%s: expected map %v, got %v", tt.name, l, tt.name) } - t.Fatal(err) - } - - require.False(t, tt.err, "Expected error in test %q", tt.name) - - // m1 and m2 are the maps we want to compare - eq := reflect.DeepEqual(l, tt.expect) - assert.True(t, eq, "%s: expected map %v, got %v", tt.name, l, tt.name) + }) } } @@ -479,8 +475,7 @@ func TestErrRepoNotFound_Error(t *testing.T) { e := ErrRepoNotFound{ Repos: tt.fields.Repos, } - got := e.Error() - assert.EqualErrorf(t, e, tt.want, "Error() = %v, want %v", got, tt.want) + assert.EqualError(t, e, tt.want) }) } } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index ecc1eee3b..1f3096366 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -78,8 +78,7 @@ func TestFuncMap(t *testing.T) { // Test for Engine-specific template functions. expect := []string{"include", "required", "tpl", "toYaml", "fromYaml", "toToml", "fromToml", "toJson", "fromJson", "lookup"} for _, f := range expect { - _, ok := fns[f] - assert.Truef(t, ok, "Expected add-on function %q", f) + assert.Containsf(t, fns, f, "Expected add-on function %q", f) } } @@ -412,8 +411,7 @@ func TestParseErrors(t *testing.T) { } _, err := new(Engine).render(t.Context(), tplsUndefinedFunction) require.Error(t, err, "Expected failures while rendering") - expected := `parse error at (undefined_function:1): function "foo" not defined` - assert.EqualError(t, err, expected) + assert.EqualError(t, err, `parse error at (undefined_function:1): function "foo" not defined`) } func TestExecErrors(t *testing.T) { @@ -887,9 +885,8 @@ func TestRenderRecursionLimit(t *testing.T) { expectErr := "rendering template has a nested reference name: recursion: unable to execute template" _, err := Render(c, v) - if err == nil || !strings.HasSuffix(err.Error(), expectErr) { - t.Errorf("Expected err with suffix: %s", expectErr) - } + require.Error(t, err) + assert.True(t, strings.HasSuffix(err.Error(), expectErr), "Expected err with suffix: %s", expectErr) // calling the same function many times is ok times := 4000 @@ -1261,18 +1258,14 @@ func TestRenderCustomTemplateFuncs(t *testing.T) { require.NoError(t, err) // Expected output should be "hello!!!". - expected := "hello!!!" - key := "CustomFunc/templates/manifest" - if rendered, ok := out[key]; !ok || rendered != expected { - t.Errorf("Expected %q, got %q", expected, rendered) - } + rendered, ok := out["CustomFunc/templates/manifest"] + require.True(t, ok) + assert.Equal(t, "hello!!!", rendered) // Verify that the rendered template used the custom "upper" function. - expected = "custom:hello" - key = "CustomFunc/templates/override" - if rendered, ok := out[key]; !ok || rendered != expected { - t.Errorf("Expected %q, got %q", expected, rendered) - } + rendered, ok = out["CustomFunc/templates/override"] + require.True(t, ok) + assert.Equal(t, "custom:hello", rendered) } func TestTraceableError_SimpleForm(t *testing.T) { diff --git a/pkg/getter/ocigetter_test.go b/pkg/getter/ocigetter_test.go index dfcc74890..5e7f01420 100644 --- a/pkg/getter/ocigetter_test.go +++ b/pkg/getter/ocigetter_test.go @@ -55,23 +55,14 @@ func TestOCIGetter(t *testing.T) { og, ok := g.(*OCIGetter) require.True(t, ok, "expected NewOCIGetter to produce an *OCIGetter") - assert.Equal(t, "I", og.opts.username, "Expected NewOCIGetter to contain %q as the username, got %q", "I", og.opts.username) - assert.Equal(t, "Am", og.opts.password, "Expected NewOCIGetter to contain %q as the password, got %q", "Am", og.opts.password) - assert.Equal(t, pub, og.opts.certFile, "Expected NewOCIGetter to contain %q as the public key file, got %q", pub, og.opts.certFile) - assert.Equal(t, priv, og.opts.keyFile, "Expected NewOCIGetter to contain %q as the private key file, got %q", priv, og.opts.keyFile) - assert.Equal(t, ca, og.opts.caFile, "Expected NewOCIGetter to contain %q as the CA file, got %q", ca, og.opts.caFile) - assert.Equal(t, timeout, og.opts.timeout, "Expected NewOCIGetter to contain %s as Timeout flag, got %s", timeout, og.opts.timeout) - assert.Equal(t, transport, og.opts.transport, "Expected NewOCIGetter to contain %p as Transport, got %p", transport, og.opts.transport) - assert.Equal(t, plainHTTP, og.opts.plainHTTP, "Expected NewOCIGetter to have plainHTTP as %t, got %t", plainHTTP, og.opts.plainHTTP) - assert.Equal(t, insecureSkipVerifyTLS, og.opts.insecureSkipVerifyTLS, "Expected NewOCIGetter to have insecureSkipVerifyTLS as %t, got %t", insecureSkipVerifyTLS, og.opts.insecureSkipVerifyTLS) // Test if setting registryClient is being passed to the ops @@ -84,7 +75,6 @@ func TestOCIGetter(t *testing.T) { require.NoError(t, err) og, ok = g.(*OCIGetter) require.True(t, ok, "expected NewOCIGetter to produce an *OCIGetter") - assert.Equal(t, registryClient, og.opts.registryClient, "Expected NewOCIGetter to contain %p as RegistryClient, got %p", registryClient, og.opts.registryClient) } diff --git a/pkg/repo/v1/index_test.go b/pkg/repo/v1/index_test.go index 88aed14d7..09fc7de8a 100644 --- a/pkg/repo/v1/index_test.go +++ b/pkg/repo/v1/index_test.go @@ -116,9 +116,8 @@ func TestIndexFile(t *testing.T) { assert.Equalf(t, "0.2.0", v, "Unexpected first version: %s", v) cv, err := i.Get("setter", "0.1.9") - if err == nil && !strings.Contains(cv.Version, "0.1.9") { - t.Errorf("Unexpected version: %s", cv.Version) - } + require.NoError(t, err) + assert.Contains(t, cv.Version, "0.1.9", "Unexpected version: %s", cv.Version) cv, err = i.Get("setter", "0.1.9+alpha") require.NoError(t, err, "Expected version: 0.1.9+alpha") @@ -304,9 +303,8 @@ func verifyLocalIndex(t *testing.T, i *IndexFile) { require.Equalf(t, 1, l, "'alpine' should have 1 chart, got %d", l) nginx, ok := i.Entries["nginx"] - if !ok || len(nginx) != 2 { - t.Fatal("Expected 2 nginx entries") - } + require.True(t, ok) + require.Len(t, nginx, 2, "Expected 2 nginx entries") expects := []*ChartVersion{ { @@ -507,18 +505,14 @@ func TestIgnoreSkippableChartValidationError(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { result := ignoreSkippableChartValidationError(tc.Input) - - if tc.Input == nil { + switch { + case tc.Input == nil: assert.NoError(t, result, "expected nil result for nil input") - return - } - - if tc.ErrorSkipped { + case tc.ErrorSkipped: assert.NoError(t, result, "expected nil result for skipped error") - return + default: + assert.ErrorIs(t, tc.Input, result, "expected the result equal to input") } - - assert.ErrorIs(t, tc.Input, result, "expected the result equal to input") }) } } diff --git a/pkg/storage/driver/cfgmaps_test.go b/pkg/storage/driver/cfgmaps_test.go index e36aecce8..b8449f67e 100644 --- a/pkg/storage/driver/cfgmaps_test.go +++ b/pkg/storage/driver/cfgmaps_test.go @@ -16,7 +16,6 @@ package driver import ( "encoding/base64" "encoding/json" - "reflect" "testing" "github.com/stretchr/testify/assert" @@ -46,7 +45,7 @@ func TestConfigMapGet(t *testing.T) { got, err := cfgmaps.Get(key) require.NoError(t, err, "Failed to get release") // compare fetched release with original - assert.Truef(t, reflect.DeepEqual(rel, got), "Expected {%v}, got {%v}", rel, got) + assert.Equalf(t, rel, got, "Expected {%v}, got {%v}", rel, got) } func TestUncompressedConfigMapGet(t *testing.T) { @@ -70,7 +69,7 @@ func TestUncompressedConfigMapGet(t *testing.T) { got, err := cfgmaps.Get(key) require.NoError(t, err, "Failed to get release") // compare fetched release with original - assert.Truef(t, reflect.DeepEqual(rel, got), "Expected {%v}, got {%v}", rel, got) + assert.Equalf(t, rel, got, "Expected {%v}, got {%v}", rel, got) } func convertReleaserToV1(t *testing.T, rel release.Releaser) *rspb.Release { @@ -105,7 +104,7 @@ func TestConfigMapList(t *testing.T) { }) // check require.NoError(t, err, "Failed to list deleted") - assert.Len(t, del, 2, "Expected 2 deleted, got %d:\n%v\n", len(del), del) + assert.Len(t, del, 2, "Expected 2 deleted") // list all deployed releases dpl, err := cfgmaps.List(func(rel release.Releaser) bool { @@ -114,7 +113,7 @@ func TestConfigMapList(t *testing.T) { }) // check require.NoError(t, err, "Failed to list deployed") - assert.Len(t, dpl, 2, "Expected 2 deployed, got %d", len(dpl)) + assert.Len(t, dpl, 2, "Expected 2 deployed") // list all superseded releases ssd, err := cfgmaps.List(func(rel release.Releaser) bool { @@ -123,13 +122,11 @@ func TestConfigMapList(t *testing.T) { }) // check require.NoError(t, err, "Failed to list superseded") - assert.Len(t, ssd, 2, "Expected 2 superseded, got %d", len(ssd)) + assert.Len(t, ssd, 2, "Expected 2 superseded") // Check if release having both system and custom labels, this is needed to ensure that selector filtering would work. rls := convertReleaserToV1(t, ssd[0]) - _, ok := rls.Labels["name"] - require.True(t, ok, "Expected 'name' label in results, actual %v", rls.Labels) - _, ok = rls.Labels["key1"] - require.True(t, ok, "Expected 'key1' label in results, actual %v", rls.Labels) + require.Contains(t, rls.Labels, "name", "Expected 'name' label in results, actual %v", rls.Labels) + require.Contains(t, rls.Labels, "key1", "Expected 'key1' label in results, actual %v", rls.Labels) } func TestConfigMapQuery(t *testing.T) { @@ -144,7 +141,7 @@ func TestConfigMapQuery(t *testing.T) { rls, err := cfgmaps.Query(map[string]string{"status": "deployed"}) require.NoError(t, err, "Failed to query") - assert.Len(t, rls, 2, "Expected 2 results, got %d", len(rls)) + assert.Len(t, rls, 2, "Expected 2 results") _, err = cfgmaps.Query(map[string]string{"name": "notExist"}) assert.ErrorIs(t, err, ErrReleaseNotFound) @@ -167,7 +164,7 @@ func TestConfigMapCreate(t *testing.T) { require.NoError(t, err, "Failed to get release with key %q", key) // compare created release with original - assert.Truef(t, reflect.DeepEqual(rel, got), "Expected {%v}, got {%v}", rel, got) + assert.Equalf(t, rel, got, "Expected {%v}, got {%v}", rel, got) } func TestConfigMapUpdate(t *testing.T) { @@ -210,7 +207,7 @@ func TestConfigMapDelete(t *testing.T) { // perform the delete rls, err := cfgmaps.Delete(key) require.NoError(t, err, "Failed to delete release with key %q", key) - assert.Truef(t, reflect.DeepEqual(rel, rls), "Expected {%v}, got {%v}", rel, rls) + assert.Equalf(t, rel, rls, "Expected {%v}, got {%v}", rel, rls) _, err = cfgmaps.Get(key) assert.ErrorIs(t, err, ErrReleaseNotFound) } diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go index 7a8d4cb3e..c0eff3cf9 100644 --- a/pkg/storage/driver/memory_test.go +++ b/pkg/storage/driver/memory_test.go @@ -18,7 +18,6 @@ package driver import ( "fmt" - "reflect" "testing" "github.com/stretchr/testify/assert" @@ -112,7 +111,7 @@ func TestMemoryList(t *testing.T) { }) // check require.NoError(t, err, "Failed to list deployed releases") - assert.Len(t, dpl, 2, "Expected 2 deployed, got %d", len(dpl)) + assert.Len(t, dpl, 2, "Expected 2 deployed") // list all superseded releases ssd, err := ts.List(func(rel release.Releaser) bool { @@ -121,7 +120,7 @@ func TestMemoryList(t *testing.T) { }) // check require.NoError(t, err, "Failed to list superseded releases") - assert.Len(t, ssd, 6, "Expected 6 superseded, got %d", len(ssd)) + assert.Len(t, ssd, 6, "Expected 6 superseded") // list all deleted releases del, err := ts.List(func(rel release.Releaser) bool { @@ -210,7 +209,7 @@ func TestMemoryUpdate(t *testing.T) { r, err := ts.Get(tt.key) require.NoError(t, err, "Failed to get") - require.Truef(t, reflect.DeepEqual(r, tt.rls), "Expected %v, actual %v\n", tt.rls, r) + require.Equalf(t, r, tt.rls, "Expected %v, actual %v\n", tt.rls, r) } } } @@ -256,10 +255,8 @@ func TestMemoryDelete(t *testing.T) { ts.SetNamespace("") end, err := ts.Query(map[string]string{"status": "deployed"}) require.NoError(t, err, "Query failed") - endLen := len(end) - if startLen-2 != endLen { - t.Errorf("expected end to be %d instead of %d", startLen-2, endLen) + if !assert.Len(t, end, startLen-2) { for _, ee := range end { rac, err := release.NewAccessor(ee) require.NoError(t, err, "unable to get release accessor") diff --git a/pkg/storage/driver/secrets_test.go b/pkg/storage/driver/secrets_test.go index 8cc3f390d..b53f17ad9 100644 --- a/pkg/storage/driver/secrets_test.go +++ b/pkg/storage/driver/secrets_test.go @@ -16,7 +16,6 @@ package driver import ( "encoding/base64" "encoding/json" - "reflect" "testing" "github.com/stretchr/testify/assert" @@ -46,7 +45,7 @@ func TestSecretGet(t *testing.T) { got, err := secrets.Get(key) require.NoError(t, err, "Failed to get release") // compare fetched release with original - assert.Truef(t, reflect.DeepEqual(rel, got), "Expected {%v}, got {%v}", rel, got) + assert.Equalf(t, rel, got, "Expected {%v}, got {%v}", rel, got) } func TestUNcompressedSecretGet(t *testing.T) { @@ -70,7 +69,7 @@ func TestUNcompressedSecretGet(t *testing.T) { got, err := secrets.Get(key) require.NoError(t, err, "Failed to get release") // compare fetched release with original - assert.Truef(t, reflect.DeepEqual(rel, got), "Expected {%v}, got {%v}", rel, got) + assert.Equalf(t, rel, got, "Expected {%v}, got {%v}", rel, got) } func TestSecretList(t *testing.T) { @@ -90,7 +89,7 @@ func TestSecretList(t *testing.T) { }) // check require.NoError(t, err, "Failed to list deleted") - assert.Len(t, del, 2, "Expected 2 deleted, got %d:\n%v\n", len(del), del) + assert.Len(t, del, 2, "Expected 2 deleted") // list all deployed releases dpl, err := secrets.List(func(rel release.Releaser) bool { @@ -99,7 +98,7 @@ func TestSecretList(t *testing.T) { }) // check require.NoError(t, err, "Failed to list deployed") - assert.Len(t, dpl, 2, "Expected 2 deployed, got %d", len(dpl)) + assert.Len(t, dpl, 2, "Expected 2 deployed") // list all superseded releases ssd, err := secrets.List(func(rel release.Releaser) bool { @@ -108,13 +107,11 @@ func TestSecretList(t *testing.T) { }) // check require.NoError(t, err, "Failed to list superseded") - assert.Len(t, ssd, 2, "Expected 2 superseded, got %d", len(ssd)) + require.Len(t, ssd, 2, "Expected 2 superseded") // Check if release having both system and custom labels, this is needed to ensure that selector filtering would work. rls := convertReleaserToV1(t, ssd[0]) - _, ok := rls.Labels["name"] - require.True(t, ok, "Expected 'name' label in results, actual %v", rls.Labels) - _, ok = rls.Labels["key1"] - require.True(t, ok, "Expected 'key1' label in results, actual %v", rls.Labels) + require.Contains(t, rls.Labels, "name", "Expected 'name' label in results, actual %v", rls.Labels) + require.Contains(t, rls.Labels, "key1", "Expected 'key1' label in results, actual %v", rls.Labels) } func TestSecretQuery(t *testing.T) { @@ -152,7 +149,7 @@ func TestSecretCreate(t *testing.T) { require.NoError(t, err, "Failed to get release with key %q", key) // compare created release with original - assert.Truef(t, reflect.DeepEqual(rel, got), "Expected {%v}, got {%v}", rel, got) + assert.Equalf(t, rel, got, "Expected {%v}, got {%v}", rel, got) } func TestSecretUpdate(t *testing.T) { @@ -195,7 +192,7 @@ func TestSecretDelete(t *testing.T) { // perform the delete rls, err := secrets.Delete(key) require.NoError(t, err, "Failed to delete release with key %q", key) - assert.Truef(t, reflect.DeepEqual(rel, rls), "Expected {%v}, got {%v}", rel, rls) + assert.Equalf(t, rel, rls, "Expected {%v}, got {%v}", rel, rls) _, err = secrets.Get(key) assert.ErrorIs(t, err, ErrReleaseNotFound) } diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go index adcc780aa..044e9df7b 100644 --- a/pkg/storage/driver/sql_test.go +++ b/pkg/storage/driver/sql_test.go @@ -99,7 +99,7 @@ func TestSQLGet(t *testing.T) { got, err := sqlDriver.Get(key) require.NoError(t, err, "Failed to get release") - assert.Truef(t, reflect.DeepEqual(rel, got), "Expected release {%v}, got {%v}", rel, got) + assert.Equalf(t, rel, got, "Expected release {%v}, got {%v}", rel, got) assert.NoErrorf(t, mock.ExpectationsWereMet(), "sql expectations weren't met") } @@ -151,7 +151,7 @@ func TestSQLList(t *testing.T) { }) // check require.NoError(t, err, "Failed to list deleted") - assert.Len(t, del, 2, "Expected 2 deleted, got %d:\n%v\n", len(del), del) + assert.Len(t, del, 2, "Expected 2 deleted") // list all deployed releases dpl, err := sqlDriver.List(func(rel release.Releaser) bool { @@ -160,7 +160,7 @@ func TestSQLList(t *testing.T) { }) // check require.NoError(t, err, "Failed to list deployed") - assert.Len(t, dpl, 2, "Expected 2 deployed, got %d:\n%v\n", len(dpl), dpl) + assert.Len(t, dpl, 2, "Expected 2 deployed") // list all superseded releases ssd, err := sqlDriver.List(func(rel release.Releaser) bool { @@ -169,15 +169,13 @@ func TestSQLList(t *testing.T) { }) // check require.NoError(t, err, "Failed to list superseded") - assert.Len(t, ssd, 2, "Expected 2 superseded, got %d:\n%v\n", len(ssd), ssd) + require.Len(t, ssd, 2, "Expected 2 superseded") require.NoErrorf(t, mock.ExpectationsWereMet(), "sql expectations weren't met") // Check if release having both system and custom labels, this is needed to ensure that selector filtering would work. rls := convertReleaserToV1(t, ssd[0]) - _, ok := rls.Labels["name"] - require.True(t, ok, "Expected 'name' label in results, actual %v", rls.Labels) - _, ok = rls.Labels["key1"] - require.True(t, ok, "Expected 'key1' label in results, actual %v", rls.Labels) + require.Contains(t, rls.Labels, "name", "Expected 'name' label in results, actual %v", rls.Labels) + require.Contains(t, rls.Labels, "key1", "Expected 'key1' label in results, actual %v", rls.Labels) } func TestSqlCreate(t *testing.T) { @@ -414,18 +412,15 @@ func TestSqlQuery(t *testing.T) { require.NoError(t, err, "failed to query for deployed smug-pigeon release") for _, res := range results { - assert.Truef(t, reflect.DeepEqual(res, deployedRelease), "Expected release {%v}, got {%v}", deployedRelease, res) + assert.Equalf(t, res, deployedRelease, "Expected release {%v}, got {%v}", deployedRelease, res) } results, err = sqlDriver.Query(labelSetAll) require.NoError(t, err, "failed to query release history for smug-pigeon") - - assert.Len(t, results, 2, "expected a resultset of size 2, got %d", len(results)) + assert.Len(t, results, 2, "expected a resultset of size 2") for _, res := range results { - if !reflect.DeepEqual(res, deployedRelease) && !reflect.DeepEqual(res, supersededRelease) { - t.Errorf("Expected release {%v} or {%v}, got {%v}", deployedRelease, supersededRelease, res) - } + assert.False(t, !reflect.DeepEqual(res, deployedRelease) && !reflect.DeepEqual(res, supersededRelease), "Expected release {%v} or {%v}, got {%v}", deployedRelease, supersededRelease, res) } assert.NoErrorf(t, mock.ExpectationsWereMet(), "sql expectations weren't met") @@ -495,7 +490,7 @@ func TestSqlDelete(t *testing.T) { assert.NoErrorf(t, err, "sql expectations weren't met") require.NoError(t, err, "failed to delete release with key %q", key) - assert.Truef(t, reflect.DeepEqual(rel, deletedRelease), "Expected release {%v}, got {%v}", rel, deletedRelease) + assert.Equalf(t, rel, deletedRelease, "Expected release {%v}, got {%v}", rel, deletedRelease) } func mockGetReleaseCustomLabels(mock sqlmock.Sqlmock, key string, namespace string, labels map[string]string) { diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index 821eccb81..58cb56776 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -20,7 +20,6 @@ import ( "context" "errors" "log/slog" - "reflect" "testing" "github.com/stretchr/testify/assert" @@ -49,7 +48,7 @@ func TestStorageCreate(t *testing.T) { require.NoError(t, err, "QueryRelease") // verify the fetched and created release are the same - require.Truef(t, reflect.DeepEqual(rls, res), "Expected %v, got %v", rls, res) + require.Equalf(t, rls, res, "Expected %v, got %v", rls, res) } func TestStorageUpdate(t *testing.T) { @@ -74,7 +73,7 @@ func TestStorageUpdate(t *testing.T) { require.NoError(t, err, "QueryRelease") // verify updated and fetched releases are the same. - require.Truef(t, reflect.DeepEqual(rls, res), "Expected %v, got %v", rls, res) + require.Equalf(t, rls, res, "Expected %v, got %v", rls, res) } func TestStorageDelete(t *testing.T) { @@ -99,7 +98,7 @@ func TestStorageDelete(t *testing.T) { require.NoError(t, err, "DeleteRelease") // verify updated and fetched releases are the same. - require.Truef(t, reflect.DeepEqual(rls, res), "Expected %v, got %v", rls, res) + require.Equalf(t, rls, res, "Expected %v, got %v", rls, res) hist, err := storage.History(rls.Name) require.NoError(t, err) @@ -348,12 +347,12 @@ func TestStorageRemoveLeastRecent(t *testing.T) { require.NoError(t, err) rhist, err := releaseListToV1List(hist) require.NoError(t, err) - if len(rhist) != storage.MaxHistory { + if !assert.Len(t, rhist, storage.MaxHistory) { for _, item := range rhist { t.Logf("%s %v", item.Name, item.Version) } - t.Fatalf("expected %d items in history, got %d", storage.MaxHistory, len(rhist)) } + require.Len(t, rhist, storage.MaxHistory) // We expect the existing records to be 3, 4, and 5. for i, item := range rhist { @@ -392,14 +391,14 @@ func TestStorageDoNotDeleteDeployed(t *testing.T) { // (the only deployed release), to still exist hist, err := storage.History(name) require.NoError(t, err) - if len(hist) != storage.MaxHistory { + if !assert.Len(t, hist, storage.MaxHistory) { rhist, err := releaseListToV1List(hist) require.NoError(t, err) for _, item := range rhist { t.Logf("%s %v", item.Name, item.Version) } - t.Fatalf("expected %d items in history, got %d", storage.MaxHistory, len(rhist)) } + require.Len(t, hist, storage.MaxHistory) expectedVersions := map[int]bool{ 2: true, From 9a2be11e6f1af0c89dc0988b88fd339751baa44c Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 24 Jul 2026 13:24:06 +0200 Subject: [PATCH 491/571] chore(pkg): refactor: finer tests conversions to testify part 3 refactor: finer tests conversions to testify in pkg/cmd Signed-off-by: Matthieu MOREL --- pkg/action/install_test.go | 6 +- pkg/action/lint_test.go | 12 ++-- pkg/action/validate_test.go | 12 ++-- pkg/chart/common/util/coalesce_test.go | 12 ++-- pkg/chart/v2/lint/rules/chartfile_test.go | 7 +-- pkg/chart/v2/lint/rules/crds_test.go | 3 +- pkg/chart/v2/lint/rules/dependencies_test.go | 3 +- pkg/chart/v2/lint/support/message_test.go | 6 +- pkg/chart/v2/util/chartfile_test.go | 8 +-- pkg/chart/v2/util/create_test.go | 7 ++- pkg/chart/v2/util/dependencies_test.go | 2 +- pkg/cmd/completion_test.go | 13 ++--- pkg/cmd/repo_index_test.go | 4 +- pkg/cmd/repo_update_test.go | 6 +- pkg/cmd/rollback_test.go | 3 +- pkg/cmd/search_hub_test.go | 12 +--- pkg/cmd/show_test.go | 10 ++-- .../chart_downloader_windows_test.go | 14 ++--- pkg/engine/files_test.go | 9 +-- pkg/gates/gates_test.go | 2 +- pkg/getter/httpgetter_test.go | 18 +++--- pkg/kube/ready_test.go | 55 +++++++++++++++---- pkg/kube/resource_test.go | 6 +- pkg/kube/wait_test.go | 6 +- pkg/kubeenv/roundtripper_test.go | 9 ++- pkg/provenance/sign_test.go | 12 +--- pkg/pusher/ocipusher_test.go | 5 +- pkg/registry/chart_test.go | 6 +- pkg/registry/client_test.go | 4 +- pkg/registry/plugin_test.go | 7 +-- pkg/registry/registry_test.go | 14 ++--- pkg/release/v1/util/manifest_sorter_test.go | 1 - pkg/repo/v1/chartrepo_test.go | 8 +-- pkg/repo/v1/repo_test.go | 14 ++--- pkg/repo/v1/repotest/server_test.go | 9 +-- pkg/storage/driver/labels_test.go | 8 ++- pkg/storage/driver/records_test.go | 5 +- pkg/storage/driver/util_test.go | 7 +-- 38 files changed, 158 insertions(+), 187 deletions(-) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index da680f41a..4dbeb7c79 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -420,9 +420,7 @@ func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) { req.NoError(err) is.Equal("with-notes", rel.Name) // test run can return as either 'parent\nchild' or 'child\nparent' - if !strings.Contains(rel.Info.Notes, "parent") && !strings.Contains(rel.Info.Notes, "child") { - t.Fatalf("Expected 'parent\nchild' or 'child\nparent', got '%s'", rel.Info.Notes) - } + req.True(strings.Contains(rel.Info.Notes, "parent") || strings.Contains(rel.Info.Notes, "child"), "Expected 'parent\nchild' or 'child\nparent', got '%s'", rel.Info.Notes) is.Equal("Install complete", rel.Info.Description) } @@ -1096,7 +1094,7 @@ func TestInstallCRDs(t *testing.T) { mockChart := buildChart(withFile(mockFile)) crdsToInstall := mockChart.CRDObjects() - assert.Len(t, crdsToInstall, 1) + require.Len(t, crdsToInstall, 1) assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) require.NoError(t, instAction.installCRDs(crdsToInstall)) } diff --git a/pkg/action/lint_test.go b/pkg/action/lint_test.go index 5c956c682..f3fd994d7 100644 --- a/pkg/action/lint_test.go +++ b/pkg/action/lint_test.go @@ -106,10 +106,8 @@ func TestNonExistentChart(t *testing.T) { testLint := NewLint() result := testLint.Run(testCharts, values) - assert.Len(t, result.Errors, 1, "expected one error, but got", len(result.Errors)) - - actual := result.Errors[0].Error() - assert.EqualError(t, result.Errors[0], expectedError, "expected '%s', but got '%s'", expectedError, actual) + require.Len(t, result.Errors, 1, "expected one error, but got", len(result.Errors)) + assert.EqualError(t, result.Errors[0], expectedError) }) t.Run("should error out for corrupted tgz chart", func(t *testing.T) { @@ -118,10 +116,8 @@ func TestNonExistentChart(t *testing.T) { testLint := NewLint() result := testLint.Run(testCharts, values) - assert.Len(t, result.Errors, 1, "expected one error, but got", len(result.Errors)) - - actual := result.Errors[0].Error() - assert.EqualError(t, result.Errors[0], expectedEOFError, "expected '%s', but got '%s'", expectedEOFError, actual) + require.Len(t, result.Errors, 1, "expected one error, but got", len(result.Errors)) + assert.EqualError(t, result.Errors[0], expectedEOFError) }) } diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go index 27097d177..8bd9f1916 100644 --- a/pkg/action/validate_test.go +++ b/pkg/action/validate_test.go @@ -132,7 +132,7 @@ func TestRequireAdoption(t *testing.T) { // Verify that a resource that lacks labels/annotations can be adopted found, err := requireAdoption(resources) require.NoError(t, err) - assert.Len(t, found, 1) + require.Len(t, found, 1) assert.Equal(t, found[0], existing) assert.NotSame(t, found[0], existing) } @@ -157,7 +157,7 @@ func TestExistingResourceConflict(t *testing.T) { // Verify only existing resources are returned found, err := existingResourceConflict(resources, releaseName, releaseNamespace) require.NoError(t, err) - assert.Len(t, found, 1) + require.Len(t, found, 1) assert.Equal(t, found[0], existing) assert.NotSame(t, found[0], existing) @@ -242,8 +242,8 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) require.NoError(t, err) - assert.Len(t, ownedList, 1) - assert.Len(t, unownedList, 1) + require.Len(t, ownedList, 1) + require.Len(t, unownedList, 1) assert.Equal(t, "owned", ownedList[0].Name) assert.Equal(t, "unowned", unownedList[0].Name) }) @@ -290,8 +290,8 @@ func TestVerifyOwnershipBeforeDelete(t *testing.T) { ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) require.NoError(t, err) - assert.Len(t, ownedList, 1) - assert.Len(t, unownedList, 1) + require.Len(t, ownedList, 1) + require.Len(t, unownedList, 1) assert.Equal(t, "owned", ownedList[0].Name) assert.Equal(t, "unowned", unownedList[0].Name) }) diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index 6ef092365..e058bd996 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -372,8 +372,7 @@ func TestMergeValues(t *testing.T) { assert.True(t, ok, "Expected nested boat key to be present but it was removed") subchart := v["pequod"].(map[string]any)["ahab"].(map[string]any) - _, ok = subchart["boat"] - assert.True(t, ok, "Expected subchart boat key to be present but it was removed") + assert.Contains(t, subchart, "boat", "Expected subchart boat key to be present but it was removed") _, ok = subchart["nested"].(map[string]any)["bar"] assert.True(t, ok, "Expected subchart nested bar key to be present but it was removed") @@ -526,8 +525,7 @@ func TestMergeTables(t *testing.T) { // This is one test that is different from CoalesceTables. Because hole // is a nil value and it's not removed it's still present. - _, ok = dst["hole"] - assert.True(t, ok, "The hole no longer exists.") + assert.Contains(t, dst, "hole", "The hole no longer exists.") dst2 := map[string]any{ "name": "Ishmael", @@ -559,8 +557,7 @@ func TestMergeTables(t *testing.T) { det2, ok := dst2["details"].(map[string]any) require.Truef(t, ok, "Details is the wrong type: %v", dst2["details"]) - _, ok = det2["friends"] - assert.True(t, ok, "Could not find your friends. Maybe you don't have any. :-(") + assert.Contains(t, det2, "friends", "Could not find your friends. Maybe you don't have any. :-(") assert.Equal(t, "pequod", dst2["boat"].(string), "Expected boat string, got %v", dst2["boat"]) assert.Equal(t, "black", dst2["hole"].(string), "Expected hole string, got %v", dst2["hole"]) assert.Nil(t, dst2["nilval"], "Expected nilvalue to have nil value but it does not") @@ -659,8 +656,7 @@ func TestCoalesceValuesEmptyMapWithNils(t *testing.T) { is.Equal("bar", data["foo"]) // "baz" should be preserved with nil value since it wasn't in chart defaults - _, ok = data["baz"] - is.True(ok, "Expected data.baz key to be present but it was removed") + is.Contains(data, "baz", "Expected data.baz key to be present but it was removed") is.Nil(data["baz"], "Expected data.baz key to be nil but it is not") } diff --git a/pkg/chart/v2/lint/rules/chartfile_test.go b/pkg/chart/v2/lint/rules/chartfile_test.go index 7c9f1c422..1cc331a74 100644 --- a/pkg/chart/v2/lint/rules/chartfile_test.go +++ b/pkg/chart/v2/lint/rules/chartfile_test.go @@ -195,7 +195,6 @@ func TestChartfile(t *testing.T) { expectedNumberOfErrorMessages := 7 require.Lenf(t, msgs, expectedNumberOfErrorMessages, "Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs)) - require.ErrorContains(t, msgs[0].Err, "name is required", "Unexpected message 0: %s", msgs[0].Err) require.ErrorContains(t, msgs[1].Err, "apiVersion is required. The value must be either \"v1\" or \"v2\"", "Unexpected message 1: %s", msgs[1].Err) require.ErrorContains(t, msgs[2].Err, "version '0.0.0.0' is not a valid SemVer", "Unexpected message 2: %s", msgs[2].Err) @@ -211,11 +210,7 @@ func TestChartfile(t *testing.T) { msgs := linter.Messages expectedNumberOfErrorMessages := 4 - if len(msgs) != expectedNumberOfErrorMessages { - t.Errorf("Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs)) - return - } - + require.Len(t, msgs, expectedNumberOfErrorMessages, "Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs)) require.ErrorContains(t, msgs[0].Err, "version should be of type string", "Unexpected message 0: %s", msgs[0].Err) require.ErrorContains(t, msgs[1].Err, "version '7.2445e+06' is not a valid SemVer", "Unexpected message 1: %s", msgs[1].Err) require.ErrorContains(t, msgs[2].Err, "appVersion should be of type string", "Unexpected message 2: %s", msgs[2].Err) diff --git a/pkg/chart/v2/lint/rules/crds_test.go b/pkg/chart/v2/lint/rules/crds_test.go index 7016c4d9a..d0790692f 100644 --- a/pkg/chart/v2/lint/rules/crds_test.go +++ b/pkg/chart/v2/lint/rules/crds_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v4/pkg/chart/v2/lint/support" ) @@ -33,7 +34,7 @@ func TestInvalidCrdsDir(t *testing.T) { Crds(&linter) res := linter.Messages - assert.Len(t, res, 1) + require.Len(t, res, 1) assert.ErrorContains(t, res[0].Err, "not a directory") } diff --git a/pkg/chart/v2/lint/rules/dependencies_test.go b/pkg/chart/v2/lint/rules/dependencies_test.go index c800887bd..4bd7b3e37 100644 --- a/pkg/chart/v2/lint/rules/dependencies_test.go +++ b/pkg/chart/v2/lint/rules/dependencies_test.go @@ -140,8 +140,7 @@ func TestDependencies(t *testing.T) { linter := support.Linter{ChartDir: filepath.Join(tmp, c.Metadata.Name)} Dependencies(&linter) - if l := len(linter.Messages); l != 2 { - t.Errorf("expected 2 linter errors for bad chart dependencies. Got %d.", l) + if !assert.Len(t, linter.Messages, 2, "expected 2 linter errors for bad chart dependencies") { for i, msg := range linter.Messages { t.Logf("Message: %d, Error: %#v", i, msg) } diff --git a/pkg/chart/v2/lint/support/message_test.go b/pkg/chart/v2/lint/support/message_test.go index dfbc65844..6d09df688 100644 --- a/pkg/chart/v2/lint/support/message_test.go +++ b/pkg/chart/v2/lint/support/message_test.go @@ -58,11 +58,11 @@ func TestRunLinterRule(t *testing.T) { func TestMessage(t *testing.T) { m := Message{ErrorSev, "Chart.yaml", errors.New("Foo")} - require.EqualErrorf(t, m, "[ERROR] Chart.yaml: Foo", "Unexpected output: %s", m.Error()) + require.EqualError(t, m, "[ERROR] Chart.yaml: Foo") m = Message{WarningSev, "templates/", errors.New("Bar")} - require.EqualErrorf(t, m, "[WARNING] templates/: Bar", "Unexpected output: %s", m.Error()) + require.EqualError(t, m, "[WARNING] templates/: Bar") m = Message{InfoSev, "templates/rc.yaml", errors.New("FooBar")} - assert.EqualErrorf(t, m, "[INFO] templates/rc.yaml: FooBar", "Unexpected output: %s", m.Error()) + assert.EqualError(t, m, "[INFO] templates/rc.yaml: FooBar") } diff --git a/pkg/chart/v2/util/chartfile_test.go b/pkg/chart/v2/util/chartfile_test.go index bdcb506da..981d0e131 100644 --- a/pkg/chart/v2/util/chartfile_test.go +++ b/pkg/chart/v2/util/chartfile_test.go @@ -49,12 +49,8 @@ func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { assert.Equal(t, "https://example.com/64x64.png", f.Icon, "Unexpected icon: %q", f.Icon) assert.Len(t, f.Keywords, 3, "Unexpected keywords") require.Len(t, f.Annotations, 2, "Unexpected annotations") - - want, got := "extravalue", f.Annotations["extrakey"] - assert.Equalf(t, want, got, "Want %q, but got %q", want, got) - - want, got = "anothervalue", f.Annotations["anotherkey"] - assert.Equalf(t, want, got, "Want %q, but got %q", want, got) + assert.Equal(t, "extravalue", f.Annotations["extrakey"]) + assert.Equal(t, "anothervalue", f.Annotations["anotherkey"]) kk := []string{"frobnitz", "sprocket", "dodad"} for i, k := range f.Keywords { diff --git a/pkg/chart/v2/util/create_test.go b/pkg/chart/v2/util/create_test.go index c85c4e3d4..69ba1336d 100644 --- a/pkg/chart/v2/util/create_test.go +++ b/pkg/chart/v2/util/create_test.go @@ -138,8 +138,11 @@ func TestValidateChartName(t *testing.T) { "abcdefghijklmnopqrstuvwxyz-_." + "ABCDEFGHIJKLMNOPQRSTUVWXYZ-_.": false, } { - if err := validateChartName(name); (err != nil) == shouldPass { - t.Errorf("test for %q failed", name) + err := validateChartName(name) + if shouldPass { + assert.NoError(t, err) + } else { + assert.Error(t, err) } } } diff --git a/pkg/chart/v2/util/dependencies_test.go b/pkg/chart/v2/util/dependencies_test.go index 2386d2c28..097663783 100644 --- a/pkg/chart/v2/util/dependencies_test.go +++ b/pkg/chart/v2/util/dependencies_test.go @@ -43,7 +43,7 @@ func TestLoadDependency(t *testing.T) { } check := func(deps []*chart.Dependency) { - assert.Len(t, deps, 2, "expected 2 dependencies, got %d", len(deps)) + require.Len(t, deps, 2, "expected 2 dependencies, got %d", len(deps)) for i, tt := range tests { assert.Equal(t, tt.Name, deps[i].Name, "expected dependency named %q, got %q", tt.Name, deps[i].Name) assert.Equal(t, tt.Version, deps[i].Version, "expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, deps[i].Version) diff --git a/pkg/cmd/completion_test.go b/pkg/cmd/completion_test.go index 219d1eed0..296ae457e 100644 --- a/pkg/cmd/completion_test.go +++ b/pkg/cmd/completion_test.go @@ -18,9 +18,9 @@ package cmd import ( "fmt" - "strings" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" chart "helm.sh/helm/v4/pkg/chart/v2" @@ -47,13 +47,10 @@ func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) { testcmd := fmt.Sprintf("__complete %s ''", cmdName) _, out, err := executeActionCommandC(storage, testcmd) require.NoError(t, err) - if !strings.Contains(out, "ShellCompDirectiveNoFileComp") != shouldBePerformed { - if shouldBePerformed { - t.Errorf("Unexpected directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName) - } else { - t.Errorf("Did not receive directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName) - } - t.Log(out) + if shouldBePerformed { + assert.NotContains(t, out, "ShellCompDirectiveNoFileComp", "Unexpected directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName) + } else { + assert.Contains(t, out, "ShellCompDirectiveNoFileComp", "Did not receive directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName) } } diff --git a/pkg/cmd/repo_index_test.go b/pkg/cmd/repo_index_test.go index 30700749b..67984f3b7 100644 --- a/pkg/cmd/repo_index_test.go +++ b/pkg/cmd/repo_index_test.go @@ -48,10 +48,10 @@ func TestRepoIndexCmd(t *testing.T) { index, err := repo.LoadIndexFile(destIndex) require.NoError(t, err) - assert.Len(t, index.Entries, 1, "expected 1 entry, got %d: %#v", len(index.Entries), index.Entries) + require.Len(t, index.Entries, 1, "expected 1 entry, got %d: %#v", len(index.Entries), index.Entries) vs := index.Entries["compressedchart"] - assert.Len(t, vs, 2, "expected 2 versions, got %d: %#v", len(vs), vs) + require.Len(t, vs, 2, "expected 2 versions") expectedVersion := "0.2.0" assert.Equal(t, expectedVersion, vs[0].Version, "expected %q, got %q", expectedVersion, vs[0].Version) diff --git a/pkg/cmd/repo_update_test.go b/pkg/cmd/repo_update_test.go index ccd8b89c6..70b6dfbef 100644 --- a/pkg/cmd/repo_update_test.go +++ b/pkg/cmd/repo_update_test.go @@ -174,10 +174,8 @@ func TestUpdateChartsFailWithError(t *testing.T) { b := bytes.NewBuffer(nil) err = updateCharts([]*repo.ChartRepository{r1, r2}, b) require.Error(t, err, "Repo update should return error because update of repository fails and 'fail-on-repo-update-fail' flag set") - var expectedErr = "failed to update the following repositories" - var receivedErr = err.Error() - require.ErrorContains(t, err, expectedErr, "Expected error (%s) but got (%s) instead", expectedErr, receivedErr) - require.ErrorContains(t, err, invalidURL, "Expected invalid URL (%s) in error message but got (%s) instead", invalidURL, receivedErr) + require.ErrorContains(t, err, "failed to update the following repositories") + require.ErrorContains(t, err, invalidURL) got := b.String() assert.Contains(t, got, "Unable to get an update", "Repo should have failed update but instead got: %q", got) diff --git a/pkg/cmd/rollback_test.go b/pkg/cmd/rollback_test.go index ff7f314c9..aa60043aa 100644 --- a/pkg/cmd/rollback_test.go +++ b/pkg/cmd/rollback_test.go @@ -18,7 +18,6 @@ package cmd import ( "fmt" - "reflect" "testing" "github.com/stretchr/testify/assert" @@ -161,5 +160,5 @@ func TestRollbackWithLabels(t *testing.T) { updatedRel, err := releaserToV1Release(updatedReli) require.NoError(t, err) - assert.Truef(t, reflect.DeepEqual(updatedRel.Labels, labels1), "Expected {%v}, got {%v}", labels1, updatedRel.Labels) + assert.Equalf(t, updatedRel.Labels, labels1, "Expected {%v}, got {%v}", labels1, updatedRel.Labels) } diff --git a/pkg/cmd/search_hub_test.go b/pkg/cmd/search_hub_test.go index 7b00cc295..a1612443a 100644 --- a/pkg/cmd/search_hub_test.go +++ b/pkg/cmd/search_hub_test.go @@ -46,11 +46,7 @@ func TestSearchHubCmd(t *testing.T) { storage := storageFixture() _, out, err := executeActionCommandC(storage, testcmd) require.NoError(t, err) - if out != expected { - t.Error("expected and actual output did not match") - t.Log(out) - t.Log(expected) - } + assert.Equal(t, expected, out, "expected and actual output did not match") } func TestSearchHubListRepoCmd(t *testing.T) { @@ -73,11 +69,7 @@ func TestSearchHubListRepoCmd(t *testing.T) { storage := storageFixture() _, out, err := executeActionCommandC(storage, testcmd) require.NoError(t, err) - if out != expected { - t.Error("expected and actual output did not match") - t.Log(out) - t.Log(expected) - } + assert.Equal(t, expected, out, "expected and actual output did not match") } func TestSearchHubOutputCompletion(t *testing.T) { diff --git a/pkg/cmd/show_test.go b/pkg/cmd/show_test.go index 3ed01d418..b7ad94d94 100644 --- a/pkg/cmd/show_test.go +++ b/pkg/cmd/show_test.go @@ -77,12 +77,10 @@ func TestShowPreReleaseChart(t *testing.T) { contentTmp, ) _, _, err := executeActionCommand(cmd) - if err != nil { - if tt.fail { - assert.ErrorContains(t, err, tt.expectedErr, "%q expected error: %s, got: %s", tt.name, tt.expectedErr, err.Error()) - return - } - t.Errorf("%q reported error: %s", tt.name, err) + if tt.fail { + assert.ErrorContains(t, err, tt.expectedErr, "%q expected error: %s, got: %s", tt.name, tt.expectedErr, err.Error()) + } else { + assert.NoError(t, err) } }) } diff --git a/pkg/downloader/chart_downloader_windows_test.go b/pkg/downloader/chart_downloader_windows_test.go index 2b6bea5eb..0c7a23d23 100644 --- a/pkg/downloader/chart_downloader_windows_test.go +++ b/pkg/downloader/chart_downloader_windows_test.go @@ -99,10 +99,8 @@ func TestParallelDownloadTo(t *testing.T) { // Verify the file has the expected size (should match the source file) sourceFile := "testdata/local-subchart-0.1.0.tgz" sourceInfo, err := os.Stat(sourceFile) - if err == nil && info.Size() != sourceInfo.Size() { - t.Errorf("Downloaded file size (%d bytes) doesn't match source file size (%d bytes)", - info.Size(), sourceInfo.Size()) - } + require.NoError(t, err) + assert.False(t, info.Size() != sourceInfo.Size(), "Downloaded file size doesn't match source file size") // Verify it's a valid tar.gz file by checking the magic bytes file, err := os.Open(expectedFile) @@ -110,10 +108,10 @@ func TestParallelDownloadTo(t *testing.T) { defer file.Close() // gzip magic bytes are 0x1f 0x8b magic := make([]byte, 2) - if n, err := file.Read(magic); err == nil && n == 2 { - if magic[0] != 0x1f || magic[1] != 0x8b { - t.Errorf("Downloaded file is not a valid gzip file (magic bytes: %x)", magic) - } + n, err := file.Read(magic) + require.NoError(t, err) + if n == 2 { + assert.False(t, magic[0] != 0x1f || magic[1] != 0x8b, "Downloaded file is not a valid gzip file (magic bytes: %x)", magic) } } diff --git a/pkg/engine/files_test.go b/pkg/engine/files_test.go index 6ff251aad..9fec2e132 100644 --- a/pkg/engine/files_test.go +++ b/pkg/engine/files_test.go @@ -16,6 +16,7 @@ limitations under the License. package engine import ( + "strconv" "testing" "github.com/stretchr/testify/assert" @@ -47,10 +48,10 @@ func TestNewFiles(t *testing.T) { assert.Len(t, files, len(cases), "Expected len() = %d, got %d", len(cases), len(files)) for i, f := range cases { - got := string(files.GetBytes(f.path)) - assert.Equalf(t, got, f.data, "%d: expected %q, got %q", i, f.data, got) - got = files.Get(f.path) - assert.Equalf(t, got, f.data, "%d: expected %q, got %q", i, f.data, got) + t.Run(strconv.Itoa(i), func(t *testing.T) { + assert.Equal(t, f.data, string(files.GetBytes(f.path))) + assert.Equal(t, f.data, files.Get(f.path)) + }) } } diff --git a/pkg/gates/gates_test.go b/pkg/gates/gates_test.go index 217ee1bbe..e57f1aa8f 100644 --- a/pkg/gates/gates_test.go +++ b/pkg/gates/gates_test.go @@ -38,7 +38,7 @@ func TestError(t *testing.T) { os.Unsetenv(name) g := Gate(name) - assert.Equal(t, "this feature has been marked as experimental and is not enabled by default. Please set HELM_EXPERIMENTAL_FEATURE=1 in your environment to use this feature", g.Error().Error()) + assert.EqualError(t, g.Error(), "this feature has been marked as experimental and is not enabled by default. Please set HELM_EXPERIMENTAL_FEATURE=1 in your environment to use this feature") } func TestString(t *testing.T) { diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index bfde5efa8..aebb6f11c 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -122,9 +122,9 @@ func TestDownload(t *testing.T) { const expectedUserAgent = "I am Groot" basicAuthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { username, password, ok := r.BasicAuth() - if !ok || username != "username" || password != "password" { - t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) - } + assert.True(t, ok, "Expected request to use basic auth") + assert.Equal(t, "username", username) + assert.Equal(t, "password", password) assert.Equal(t, expectedUserAgent, r.UserAgent(), "Expected '%s', got '%s'", expectedUserAgent, r.UserAgent()) fmt.Fprint(w, expect) })) @@ -147,9 +147,9 @@ func TestDownload(t *testing.T) { // test with Get URL differing from withURL crossAuthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { username, password, ok := r.BasicAuth() - if ok || username == "username" || password == "password" { - t.Errorf("Expected request to not include but got '%v', '%s', '%s'", ok, username, password) - } + assert.False(t, ok) + assert.NotEqual(t, "username", username) + assert.NotEqual(t, "password", password) fmt.Fprint(w, expect) })) @@ -176,9 +176,9 @@ func TestDownload(t *testing.T) { // test with Get URL differing from withURL and should pass creds crossAuthSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { username, password, ok := r.BasicAuth() - if !ok || username != "username" || password != "password" { - t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) - } + assert.True(t, ok, "Expected request to use basic auth") + assert.Equal(t, "username", username) + assert.Equal(t, "password", password) fmt.Fprint(w, expect) })) diff --git a/pkg/kube/ready_test.go b/pkg/kube/ready_test.go index 01f6db3cb..c40ad1d4d 100644 --- a/pkg/kube/ready_test.go +++ b/pkg/kube/ready_test.go @@ -165,7 +165,11 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) { require.NoError(t, err, "Failed to create Job") got, err := c.IsReady(tt.args.ctx, tt.args.resource) - assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + require.Error(t, err, "IsReady()") + } else { + require.NoError(t, err, "IsReady()") + } assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } @@ -237,7 +241,11 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) { require.NoError(t, err, "Failed to create ReplicaSet") got, err := c.IsReady(tt.args.ctx, tt.args.resource) - assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + require.Error(t, err, "IsReady()") + } else { + require.NoError(t, err, "IsReady()") + } assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } @@ -303,7 +311,11 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) { require.NoError(t, err, "Failed to create PersistentVolumeClaim") got, err := c.IsReady(tt.args.ctx, tt.args.resource) - assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + require.Error(t, err, "IsReady()") + } else { + require.NoError(t, err, "IsReady()") + } assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } @@ -369,7 +381,11 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) { require.NoError(t, err, "Failed to create Service") got, err := c.IsReady(tt.args.ctx, tt.args.resource) - assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + require.Error(t, err, "IsReady()") + } else { + require.NoError(t, err, "IsReady()") + } assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } @@ -435,7 +451,11 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) { require.NoError(t, err, "Failed to create DaemonSet") got, err := c.IsReady(tt.args.ctx, tt.args.resource) - assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + require.Error(t, err, "IsReady()") + } else { + require.NoError(t, err, "IsReady()") + } assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } @@ -501,7 +521,11 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) { require.NoError(t, err, "Failed to create StatefulSet") got, err := c.IsReady(tt.args.ctx, tt.args.resource) - assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + require.Error(t, err, "IsReady()") + } else { + require.NoError(t, err, "IsReady()") + } assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } @@ -582,7 +606,11 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) { require.NoError(t, err, "Failed to create ReplicationController") got, err := c.IsReady(tt.args.ctx, tt.args.resource) - assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + require.Error(t, err, "IsReady()") + } else { + require.NoError(t, err, "IsReady()") + } assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } @@ -646,7 +674,11 @@ func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) { } // got, err := c.IsReady(tt.args.ctx, tt.args.resource) - assert.Equal(t, tt.wantErr, (err != nil), "IsReady() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + require.Error(t, err, "IsReady()") + } else { + require.NoError(t, err, "IsReady()") + } assert.Equal(t, tt.want, got, "IsReady() = %v, want %v", got, tt.want) }) } @@ -1044,9 +1076,10 @@ func Test_ReadyChecker_jobReady(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := NewReadyChecker(fake.NewClientset()) got, err := c.jobReady(tt.args.job) - if (err != nil) != tt.wantErr { - t.Errorf("jobReady() error = %v, wantErr %v", err, tt.wantErr) - return + if tt.wantErr { + require.Error(t, err, "jobReady()") + } else { + require.NoError(t, err, "jobReady()") } assert.Equal(t, tt.want, got, "jobReady() = %v, want %v", got, tt.want) }) diff --git a/pkg/kube/resource_test.go b/pkg/kube/resource_test.go index ca41a06ed..18501d4e8 100644 --- a/pkg/kube/resource_test.go +++ b/pkg/kube/resource_test.go @@ -42,13 +42,11 @@ func TestResourceList(t *testing.T) { diff := r1.Difference(r2) assert.Len(t, diff, 1, "expected 1 result") - - assert.True(t, diff.Contains(info("foo")), "expected diff to return foo") + assert.Contains(t, diff, info("foo"), "expected diff to return foo") inter := r1.Intersect(r2) assert.Len(t, inter, 1, "expected 1 result") - - assert.True(t, inter.Contains(info("bar")), "expected intersect to return bar") + assert.Contains(t, inter, info("bar"), "expected intersect to return bar") } func TestIsMatchingInfo(t *testing.T) { diff --git a/pkg/kube/wait_test.go b/pkg/kube/wait_test.go index 9cf1e5506..d33a12c87 100644 --- a/pkg/kube/wait_test.go +++ b/pkg/kube/wait_test.go @@ -297,8 +297,7 @@ func TestLegacyWaiter_waitForPodSuccess(t *testing.T) { t.Run(tt.name, func(t *testing.T) { done, err := lw.waitForPodSuccess(tt.obj, "foo") if tt.wantErr { - require.Error(t, err, "expected error, got none") - require.ErrorContains(t, err, tt.errMessage, "expected error to contain %q, got %q", tt.errMessage, err.Error()) + require.ErrorContains(t, err, tt.errMessage) } else { require.NoError(t, err) } @@ -384,8 +383,7 @@ func TestLegacyWaiter_waitForJob(t *testing.T) { t.Run(tt.name, func(t *testing.T) { done, err := lw.waitForJob(tt.obj, "test-job") if tt.wantErr { - require.Error(t, err, "expected error, got none") - require.ErrorContainsf(t, err, tt.errMessage, "expected error to contain %q, got %q", tt.errMessage, err.Error()) + require.ErrorContains(t, err, tt.errMessage) } else { require.NoError(t, err) assert.Equal(t, tt.wantDone, done, "got done=%v, want %v", done, tt.wantDone) diff --git a/pkg/kubeenv/roundtripper_test.go b/pkg/kubeenv/roundtripper_test.go index 6f0caf6f8..8d5b80e39 100644 --- a/pkg/kubeenv/roundtripper_test.go +++ b/pkg/kubeenv/roundtripper_test.go @@ -141,12 +141,11 @@ func TestRetryingRoundTripper_RoundTrip(t *testing.T) { if tt.expectedErr != "" { require.ErrorContains(t, err, tt.expectedErr) - return + } else { + require.NoError(t, err) + assert.Equal(t, tt.expectedCode, resp.StatusCode) + assert.Equal(t, tt.expectedCalls, fakeRT.calls) } - require.NoError(t, err) - - assert.Equal(t, tt.expectedCode, resp.StatusCode) - assert.Equal(t, tt.expectedCalls, fakeRT.calls) }) } } diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go index 32253df90..ec6e6194b 100644 --- a/pkg/provenance/sign_test.go +++ b/pkg/provenance/sign_test.go @@ -128,9 +128,7 @@ func TestParseMessageBlock(t *testing.T) { func TestLoadKey(t *testing.T) { k, err := loadKey(testKeyfile) require.NoError(t, err) - - _, ok := k.Identities[testKeyName] - assert.Truef(t, ok, "Expected to load a key for user %q", testKeyName) + assert.Containsf(t, k.Identities, testKeyName, "Expected to load a key for user %q", testKeyName) } func TestLoadKeyRing(t *testing.T) { @@ -163,8 +161,7 @@ func TestNewFromFiles(t *testing.T) { s, err := NewFromFiles(testKeyfile, testPubfile) require.NoError(t, err) - _, ok := s.Entity.Identities[testKeyName] - assert.Truef(t, ok, "Expected to load a key for user %q", testKeyName) + assert.Containsf(t, s.Entity.Identities, testKeyName, "Expected to load a key for user %q", testKeyName) } func TestDigestFile(t *testing.T) { @@ -240,7 +237,6 @@ func TestMixedKeyringRSASigningAndVerification(t *testing.T) { } assert.True(t, hasEdDSA, "expected %s to include an Ed25519 public key", testMixedKeyring) - require.NotNil(t, signer.Entity, "expected signer entity to be loaded") require.NotNil(t, signer.Entity.PrivateKey, "expected signer private key to be loaded") assert.Equal(t, packet.PubKeyAlgoRSA, signer.Entity.PrivateKey.PubKeyAlgo, "expected RSA key") @@ -259,9 +255,7 @@ func TestMixedKeyringRSASigningAndVerification(t *testing.T) { require.NotNil(t, verification.SignedBy, "expected verification to include signer") require.NotNil(t, verification.SignedBy.PrimaryKey, "expected verification to include signer primary key") assert.Equal(t, packet.PubKeyAlgoRSA, verification.SignedBy.PrimaryKey.PubKeyAlgo, "expected verification to report RSA key") - - _, ok := verification.SignedBy.Identities[testKeyName] - assert.True(t, ok, "expected verification to be signed by %q", testKeyName) + assert.Contains(t, verification.SignedBy.Identities, testKeyName, "expected verification to be signed by %q", testKeyName) } // failSigner always fails to sign and returns an error diff --git a/pkg/pusher/ocipusher_test.go b/pkg/pusher/ocipusher_test.go index ceb61d442..b63077293 100644 --- a/pkg/pusher/ocipusher_test.go +++ b/pkg/pusher/ocipusher_test.go @@ -33,8 +33,7 @@ func TestNewOCIPusher(t *testing.T) { p, err := NewOCIPusher() require.NoError(t, err) - _, ok := p.(*OCIPusher) - require.True(t, ok, "Expected NewOCIPusher to produce an *OCIPusher") + require.IsType(t, &OCIPusher{}, p, "Expected NewOCIPusher to produce an *OCIPusher") cd := "../../testdata" join := filepath.Join @@ -196,7 +195,7 @@ func TestOCIPusher_newRegistryClient(t *testing.T) { if tt.expectError { require.Error(t, err, "Expected error but got none") if tt.errorContains != "" { - require.ErrorContainsf(t, err, tt.errorContains, "Expected error containing %q, got %q", tt.errorContains, err.Error()) + require.ErrorContains(t, err, tt.errorContains) } } else { require.NoError(t, err) diff --git a/pkg/registry/chart_test.go b/pkg/registry/chart_test.go index 95e429a0b..3edf21e38 100644 --- a/pkg/registry/chart_test.go +++ b/pkg/registry/chart_test.go @@ -232,8 +232,7 @@ func TestGenerateOCICreatedAnnotations(t *testing.T) { result := generateOCIAnnotations(testChart, nowTimeString) // Check that created annotation exists - _, ok := result[ocispec.AnnotationCreated] - assert.True(t, ok, "%s annotation not created", ocispec.AnnotationCreated) + assert.Contains(t, result, ocispec.AnnotationCreated, "%s annotation not created", ocispec.AnnotationCreated) // Verify value of created artifact in RFC3339 format _, err := time.Parse(time.RFC3339, result[ocispec.AnnotationCreated]) @@ -243,8 +242,7 @@ func TestGenerateOCICreatedAnnotations(t *testing.T) { result = generateOCIAnnotations(testChart, "") // Check that created annotation exists - _, ok = result[ocispec.AnnotationCreated] - require.True(t, ok, "%s annotation not created", ocispec.AnnotationCreated) + require.Contains(t, result, ocispec.AnnotationCreated, "%s annotation not created", ocispec.AnnotationCreated) createdTimeAnnotation, err := time.Parse(time.RFC3339, result[ocispec.AnnotationCreated]) require.NoError(t, err, "%s annotation with value '%s' not in RFC3339 format", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated]) diff --git a/pkg/registry/client_test.go b/pkg/registry/client_test.go index 97b7c7c4c..21604ce7e 100644 --- a/pkg/registry/client_test.go +++ b/pkg/registry/client_test.go @@ -65,9 +65,9 @@ func TestLogin_ResetsForceAttemptOAuth2_OnSuccess(t *testing.T) { if r.URL.Path == "/v2/" { // Accept either HEAD or GET w.WriteHeader(http.StatusOK) - return + } else { + http.NotFound(w, r) } - http.NotFound(w, r) })) defer srv.Close() diff --git a/pkg/registry/plugin_test.go b/pkg/registry/plugin_test.go index bb557c5b8..d51cfa0d6 100644 --- a/pkg/registry/plugin_test.go +++ b/pkg/registry/plugin_test.go @@ -78,11 +78,10 @@ func TestGetPluginName(t *testing.T) { if tt.expectErr { assert.Error(t, err, "expected error but got none") - return + } else { + require.NoError(t, err) + assert.Equal(t, tt.expected, pluginName) } - - require.NoError(t, err) - assert.Equal(t, tt.expected, pluginName) }) } } diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index 313cb861c..40ebe9687 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -376,23 +376,21 @@ func initFakeRegistryTestServer() string { if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) - return + } else { + w.Header().Set("Content-Type", ProvLayerMediaType) + w.Write(data) } - w.Header().Set("Content-Type", ProvLayerMediaType) - w.Write(data) - case "/v2/testrepo/image-index/blobs/sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55": data, err := os.ReadFile("../downloader/testdata/signtest-0.1.0.tgz") if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) - return + } else { + w.Header().Set("Content-Type", ChartLayerMediaType) + w.Write(data) } - w.Header().Set("Content-Type", ChartLayerMediaType) - w.Write(data) - default: w.WriteHeader(http.StatusNotFound) } diff --git a/pkg/release/v1/util/manifest_sorter_test.go b/pkg/release/v1/util/manifest_sorter_test.go index 35c7a0721..da08ab169 100644 --- a/pkg/release/v1/util/manifest_sorter_test.go +++ b/pkg/release/v1/util/manifest_sorter_test.go @@ -143,7 +143,6 @@ metadata: // This test will fail if 'six' or 'seven' was added. assert.Len(t, generic, 2) - assert.Len(t, hs, 4) for _, out := range hs { diff --git a/pkg/repo/v1/chartrepo_test.go b/pkg/repo/v1/chartrepo_test.go index 4be2ebf73..54275e28d 100644 --- a/pkg/repo/v1/chartrepo_test.go +++ b/pkg/repo/v1/chartrepo_test.go @@ -175,9 +175,7 @@ func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) { // versions of Darwin do not. As there are people developing Helm using both old and new versions of Darwin we test // for both messages. if runtime.GOOS == "darwin" { - if !strings.Contains(err.Error(), "x509: “Acme Co” certificate is not trusted") && !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { - t.Errorf("Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err) - } + assert.True(t, strings.Contains(err.Error(), "x509: “Acme Co” certificate is not trusted") || strings.Contains(err.Error(), "x509: certificate signed by unknown authority"), "Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err) } else { assert.ErrorContainsf(t, err, "x509: certificate signed by unknown authority", "Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error") } @@ -203,7 +201,6 @@ func TestErrorFindChartInRepoURL(t *testing.T) { }) _, err := FindChartInRepoURL("http://someserver/something", "nginx", g) - require.Error(t, err, "Expected error for bad chart URL, but did not get any errors") require.ErrorContainsf(t, err, `looks like "http://someserver/something" is not a valid chart repository or cannot be reached`, "Expected error for bad chart URL, but got a different error") srv, err := startLocalServerForTests(nil) @@ -211,16 +208,13 @@ func TestErrorFindChartInRepoURL(t *testing.T) { defer srv.Close() _, err = FindChartInRepoURL(srv.URL, "nginx1", g) - require.Error(t, err, "Expected error for chart not found, but did not get any errors") require.EqualError(t, err, `chart "nginx1" not found in `+srv.URL+` repository`, "Expected error for chart not found, but got a different error") require.ErrorIs(t, err, ChartNotFoundError{}, "error is not of correct error type structure") _, err = FindChartInRepoURL(srv.URL, "nginx1", g, WithChartVersion("0.1.0")) - require.Error(t, err, "Expected error for chart not found, but did not get any errors") require.EqualError(t, err, `chart "nginx1" version "0.1.0" not found in `+srv.URL+` repository`, "Expected error for chart not found, but got a different error") _, err = FindChartInRepoURL(srv.URL, "chartWithNoURL", g) - require.Error(t, err, "Expected error for no chart URLs available, but did not get any errors") assert.EqualError(t, err, `chart "chartWithNoURL" has no downloadable URLs`, "Expected error for chart not found, but got a different error") } diff --git a/pkg/repo/v1/repo_test.go b/pkg/repo/v1/repo_test.go index e3373fc49..f7f5df9be 100644 --- a/pkg/repo/v1/repo_test.go +++ b/pkg/repo/v1/repo_test.go @@ -120,11 +120,8 @@ func TestRemoveRepository(t *testing.T) { ) removeRepository := "stable" - found := sampleRepository.Remove(removeRepository) - assert.Truef(t, found, "expected repository %s not found", removeRepository) - - found = sampleRepository.Has(removeRepository) - assert.Falsef(t, found, "repository %s not deleted", removeRepository) + assert.Truef(t, sampleRepository.Remove(removeRepository), "expected repository %s not found", removeRepository) + assert.Falsef(t, sampleRepository.Has(removeRepository), "repository %s not deleted", removeRepository) } func TestUpdateRepository(t *testing.T) { @@ -205,9 +202,6 @@ func TestRemoveRepositoryInvalidEntries(t *testing.T) { ) removeRepository := "stable" - found := sampleRepository.Remove(removeRepository) - assert.Truef(t, found, "expected repository %s not found", removeRepository) - - found = sampleRepository.Has(removeRepository) - assert.Falsef(t, found, "repository %s not deleted", removeRepository) + assert.Truef(t, sampleRepository.Remove(removeRepository), "expected repository %s not found", removeRepository) + assert.Falsef(t, sampleRepository.Has(removeRepository), "repository %s not deleted", removeRepository) } diff --git a/pkg/repo/v1/repotest/server_test.go b/pkg/repo/v1/repotest/server_test.go index 7008e2305..f74adb445 100644 --- a/pkg/repo/v1/repotest/server_test.go +++ b/pkg/repo/v1/repotest/server_test.go @@ -42,24 +42,25 @@ func TestServer(t *testing.T) { c, err := srv.CopyCharts("testdata/*.tgz") require.NoError(t, err) - - assert.Len(t, c, 1) - + require.Len(t, c, 1) assert.Equal(t, "examplechart-0.1.0.tgz", filepath.Base(c[0])) req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL()+"/examplechart-0.1.0.tgz", http.NoBody) require.NoError(t, err) + client := http.DefaultClient res, err := client.Do(req) require.NoError(t, err) - res.Body.Close() + res.Body.Close() assert.GreaterOrEqual(t, res.ContentLength, int64(500)) req, err = http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL()+"/index.yaml", http.NoBody) require.NoError(t, err) + res, err = client.Do(req) require.NoError(t, err) + data, err := io.ReadAll(res.Body) res.Body.Close() require.NoError(t, err) diff --git a/pkg/storage/driver/labels_test.go b/pkg/storage/driver/labels_test.go index 848ae8861..481b8c467 100644 --- a/pkg/storage/driver/labels_test.go +++ b/pkg/storage/driver/labels_test.go @@ -18,6 +18,8 @@ package driver import ( "testing" + + "github.com/stretchr/testify/require" ) func TestLabelsMatch(t *testing.T) { @@ -42,8 +44,8 @@ func TestLabelsMatch(t *testing.T) { } for _, tt := range tests { - if !tt.set1.match(tt.set2) && tt.expect { - t.Fatalf("Expected match '%s'\n", tt.desc) - } + t.Run(tt.desc, func(t *testing.T) { + require.False(t, !tt.set1.match(tt.set2) && tt.expect) + }) } } diff --git a/pkg/storage/driver/records_test.go b/pkg/storage/driver/records_test.go index 067e31e76..22d54e6cc 100644 --- a/pkg/storage/driver/records_test.go +++ b/pkg/storage/driver/records_test.go @@ -17,7 +17,6 @@ limitations under the License. package driver import ( - "reflect" "testing" "github.com/stretchr/testify/assert" @@ -132,7 +131,7 @@ func TestRecordsGet(t *testing.T) { for _, tt := range tests { got := rs.Get(tt.key) - require.Truef(t, reflect.DeepEqual(tt.rec, got), "Expected %v, got %v", tt.rec, got) + require.Equal(t, tt.rec, got, "Expected %v, got %v", tt.rec, got) } } @@ -222,6 +221,6 @@ func TestRecordsReplace(t *testing.T) { for _, tt := range tests { got := rs.Replace(tt.key, tt.rec) - require.Truef(t, reflect.DeepEqual(tt.expected, got), "Expected %v, got %v", tt.expected, got) + require.Equalf(t, tt.expected, got, "Expected %v, got %v", tt.expected, got) } } diff --git a/pkg/storage/driver/util_test.go b/pkg/storage/driver/util_test.go index 7074f252a..5dda3cd1f 100644 --- a/pkg/storage/driver/util_test.go +++ b/pkg/storage/driver/util_test.go @@ -14,7 +14,6 @@ limitations under the License. package driver import ( - "reflect" "testing" "github.com/stretchr/testify/assert" @@ -22,7 +21,7 @@ import ( func TestGetSystemLabel(t *testing.T) { output := GetSystemLabels() - assert.Truef(t, reflect.DeepEqual(systemLabels, output), "Expected {%v}, got {%v}", systemLabels, output) + assert.Equalf(t, systemLabels, output, "Expected {%v}, got {%v}", systemLabels, output) } func TestIsSystemLabel(t *testing.T) { @@ -69,7 +68,7 @@ func TestFilterSystemLabels(t *testing.T) { } for _, test := range tests { output := filterSystemLabels(test[0]) - assert.Truef(t, reflect.DeepEqual(test[1], output), "Expected {%v}, got {%v}", test[1], output) + assert.Equalf(t, test[1], output, "Expected {%v}, got {%v}", test[1], output) } } @@ -101,6 +100,6 @@ func TestContainsSystemLabels(t *testing.T) { } for _, test := range tests { output := ContainsSystemLabels(test.input) - assert.Truef(t, reflect.DeepEqual(test.output, output), "Expected {%v}, got {%v}", test.output, output) + assert.Equalf(t, test.output, output, "Expected {%v}, got {%v}", test.output, output) } } From 828d01a8786162987482408b7803ceb3f3c176ac Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 24 Jul 2026 13:24:06 +0200 Subject: [PATCH 492/571] chore(pkg): refactor: finer tests conversions to testify part 1 refactor: finer tests conversions to testify in pkg/strvals Signed-off-by: Matthieu MOREL --- pkg/action/upgrade_test.go | 25 +-- pkg/chart/v2/lint/rules/template_test.go | 29 ++-- pkg/chart/v2/loader/load_test.go | 37 ++--- pkg/downloader/chart_downloader_test.go | 65 ++++---- pkg/kube/client_test.go | 22 ++- pkg/strvals/literal_parser_test.go | 81 ++++------ pkg/strvals/parser_test.go | 189 +++++++++-------------- 7 files changed, 177 insertions(+), 271 deletions(-) diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index b7e25206d..53419b6a8 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -252,11 +252,8 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { // Now make sure it is actually upgraded updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2) req.NoError(err) + req.NotNil(updatedResi, "Updated Release is nil") - if updatedResi == nil { - is.Fail("Updated Release is nil") - return - } updatedRes, err := releaserToV1Release(updatedResi) req.NoError(err) @@ -320,11 +317,8 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { // Now get the upgraded release updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2) req.NoError(err) + req.NotNil(updatedResi, "Updated Release is nil") - if updatedResi == nil { - is.Fail("Updated Release is nil") - return - } updatedRes, err := releaserToV1Release(updatedResi) req.NoError(err) @@ -383,11 +377,8 @@ func TestUpgradeRelease_ResetThenReuseValues(t *testing.T) { // Now make sure it is actually upgraded updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2) req.NoError(err) + req.NotNil(updatedResi, "Updated Release is nil") - if updatedResi == nil { - is.Fail("Updated Release is nil") - return - } updatedRes, err := releaserToV1Release(updatedResi) req.NoError(err) @@ -523,11 +514,8 @@ func TestUpgradeRelease_Labels(t *testing.T) { // Now make sure it is actually upgraded and labels were merged updatedResi, err := upAction.cfg.Releases.Get(res.Name, 2) req.NoError(err) + req.NotNil(updatedResi, "Updated Release is nil") - if updatedResi == nil { - is.Fail("Updated Release is nil") - return - } updatedRes, err := releaserToV1Release(updatedResi) req.NoError(err) is.Equal(common.StatusDeployed, updatedRes.Info.Status) @@ -536,11 +524,8 @@ func TestUpgradeRelease_Labels(t *testing.T) { // Now make sure it is suppressed release still contains original labels initialResi, err := upAction.cfg.Releases.Get(res.Name, 1) req.NoError(err) + req.NotNil(initialResi, "Initial Release is nil") - if initialResi == nil { - is.Fail("Updated Release is nil") - return - } initialRes, err := releaserToV1Release(initialResi) req.NoError(err) is.Equal(common.StatusSuperseded, initialRes.Info.Status) diff --git a/pkg/chart/v2/lint/rules/template_test.go b/pkg/chart/v2/lint/rules/template_test.go index 37a33a7eb..ab78450b9 100644 --- a/pkg/chart/v2/lint/rules/template_test.go +++ b/pkg/chart/v2/lint/rules/template_test.go @@ -171,8 +171,11 @@ func TestValidateMetadataName(t *testing.T) { } for _, tt := range tests { t.Run(fmt.Sprintf("%s/%s", tt.obj.Kind, tt.obj.Metadata.Name), func(t *testing.T) { - if err := validateMetadataName(tt.obj); (err != nil) != tt.wantErr { - t.Errorf("validateMetadataName() error = %v, wantErr %v", err, tt.wantErr) + err := validateMetadataName(tt.obj) + if tt.wantErr { + require.Error(t, err, "validateMetadataName()") + } else { + require.NoError(t, err, "validateMetadataName()") } }) } @@ -210,12 +213,12 @@ func TestDeprecatedAPIFails(t *testing.T) { namespace, values, TemplateLinterSkipSchemaValidation(false)) - if l := len(linter.Messages); l != 1 { + if !assert.Len(t, linter.Messages, 1) { for i, msg := range linter.Messages { t.Logf("Message %d: %s", i, msg) } - t.Fatalf("Expected 1 lint error, got %d", l) } + require.Len(t, linter.Messages, 1, "Expected 1 lint error") var depErr deprecatedAPIError require.ErrorAs(t, linter.Messages[0].Err, &depErr) @@ -267,8 +270,7 @@ func TestStrictTemplateParsingMapError(t *testing.T) { namespace, ch.Values, TemplateLinterSkipSchemaValidation(false)) - if len(linter.Messages) != 0 { - t.Errorf("expected zero messages, got %d", len(linter.Messages)) + if !assert.Empty(t, linter.Messages, "expected zero messages") { for i, msg := range linter.Messages { t.Logf("Message %d: %q", i, msg) } @@ -358,9 +360,14 @@ func TestValidateTopIndentLevel(t *testing.T) { " apiVersion:foo": true, "\n\n apiVersion:foo\n\n": true, } { - if err := validateTopIndentLevel(doc); (err == nil) == shouldFail { - t.Errorf("Expected %t for %q", shouldFail, doc) - } + t.Run(doc, func(t *testing.T) { + err := validateTopIndentLevel(doc) + if shouldFail { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) } } @@ -392,12 +399,12 @@ func TestEmptyWithCommentsManifests(t *testing.T) { namespace, values, TemplateLinterSkipSchemaValidation(false)) - if l := len(linter.Messages); l > 0 { + if !assert.Empty(t, linter.Messages) { for i, msg := range linter.Messages { t.Logf("Message %d: %s", i, msg) } - t.Fatalf("Expected 0 lint errors, got %d", l) } + require.Empty(t, linter.Messages, "Expected 0 lint errors") } func TestValidateListAnnotations(t *testing.T) { md := &k8sYamlStruct{ diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index da5c3b725..4d73cb0d6 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -25,7 +25,6 @@ import ( "log" "os" "path/filepath" - "reflect" "runtime" "strings" "testing" @@ -385,11 +384,13 @@ func TestLoadInvalidArchive(t *testing.T) { {"illegal-abspath5.tgz", "/./c://foo", "chart contains illegally named files"}, {"illegal-abspath6.tgz", "\\\\?\\Some\\windows\\magic", "chart illegally contains absolute paths"}, } { - illegalChart := filepath.Join(tmpdir, tt.chartname) - writeTar(illegalChart, tt.internal, []byte("hello: world")) - _, err := Load(illegalChart) - require.Error(t, err, "expected error when unpacking illegal files") - require.ErrorContains(t, err, tt.expectError, "Expected error to contain %q, got %q for %s", tt.expectError, err.Error(), tt.chartname) + t.Run(tt.chartname, func(t *testing.T) { + illegalChart := filepath.Join(tmpdir, tt.chartname) + writeTar(illegalChart, tt.internal, []byte("hello: world")) + _, err := Load(illegalChart) + require.Error(t, err, "expected error when unpacking illegal files") + require.ErrorContains(t, err, tt.expectError) + }) } // Make sure that absolute path gets interpreted as relative @@ -456,7 +457,7 @@ foo: t.Run(testName, func(tt *testing.T) { values, err := LoadValues(bytes.NewReader(testCase.data)) require.NoError(tt, err) - assert.Truef(t, reflect.DeepEqual(values, testCase.expctedValues), "Expected values: %v, got %v", testCase.expctedValues, values) + assert.Equal(t, testCase.expctedValues, values) }) } } @@ -484,16 +485,13 @@ func TestMergeValuesV2(t *testing.T) { } testMap := MergeMaps(flatMap, nestedMap) - equal := reflect.DeepEqual(testMap, nestedMap) - assert.True(t, equal, "Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) + assert.Equal(t, testMap, nestedMap, "Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) testMap = MergeMaps(nestedMap, flatMap) - equal = reflect.DeepEqual(testMap, flatMap) - assert.True(t, equal, "Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) + assert.Equal(t, testMap, flatMap, "Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) testMap = MergeMaps(nestedMap, anotherNestedMap) - equal = reflect.DeepEqual(testMap, anotherNestedMap) - assert.True(t, equal, "Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) + assert.Equal(t, testMap, anotherNestedMap, "Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) testMap = MergeMaps(anotherFlatMap, anotherNestedMap) expectedMap := map[string]any{ @@ -504,8 +502,7 @@ func TestMergeValuesV2(t *testing.T) { "awesome": "stuff", }, } - equal = reflect.DeepEqual(testMap, expectedMap) - assert.True(t, equal, "Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) + assert.Equal(t, expectedMap, testMap, "Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) } func verifyChart(t *testing.T, c *chart.Chart) { @@ -515,15 +512,13 @@ func verifyChart(t *testing.T, c *chart.Chart) { assert.Len(t, c.Templates, 1, "Expected 1 template, got %d", len(c.Templates)) numfiles := 6 - if len(c.Files) != numfiles { - t.Errorf("Expected %d extra files, got %d", numfiles, len(c.Files)) + if !assert.Len(t, c.Files, numfiles) { for _, n := range c.Files { t.Logf("\t%s", n.Name) } } - if len(c.Dependencies()) != 2 { - t.Errorf("Expected 2 dependencies, got %d (%v)", len(c.Dependencies()), c.Dependencies()) + if !assert.Len(t, c.Dependencies(), 2, "Expected 2 dependencies") { for _, d := range c.Dependencies() { t.Logf("\tSubchart: %s\n", d.Name()) } @@ -548,7 +543,7 @@ func verifyChart(t *testing.T, c *chart.Chart) { func verifyDependencies(t *testing.T, c *chart.Chart) { t.Helper() - assert.Len(t, c.Metadata.Dependencies, 2, "Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) + require.Len(t, c.Metadata.Dependencies, 2, "Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) tests := []*chart.Dependency{ {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, @@ -563,7 +558,7 @@ func verifyDependencies(t *testing.T, c *chart.Chart) { func verifyDependenciesLock(t *testing.T, c *chart.Chart) { t.Helper() - assert.Len(t, c.Metadata.Dependencies, 2, "Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) + require.Len(t, c.Metadata.Dependencies, 2, "Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) tests := []*chart.Dependency{ {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index 7187bdff1..92d4f024d 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -82,16 +82,15 @@ func TestResolveChartRef(t *testing.T) { } for _, tt := range tests { - _, u, err := c.ResolveChartVersion(tt.ref, tt.version) - if err != nil { - if tt.fail { - continue + t.Run(tt.name, func(t *testing.T) { + _, u, err := c.ResolveChartVersion(tt.ref, tt.version) + if err != nil { + require.True(t, tt.fail) + } else { + got := u.String() + assert.Equalf(t, tt.expect, got, "%s: expected %s, got %s", tt.name, tt.expect, got) } - t.Errorf("%s: failed with error %q", tt.name, err) - continue - } - got := u.String() - assert.Equalf(t, tt.expect, got, "%s: expected %s, got %s", tt.name, tt.expect, got) + }) } } @@ -124,35 +123,25 @@ func TestResolveChartOpts(t *testing.T) { snapshotOpts := c.Options for _, tt := range tests { - // reset chart downloader options for each test case - c.Options = snapshotOpts - - expect, err := getter.NewHTTPGetter(tt.expect...) - if err != nil { - t.Errorf("%s: failed to setup http client: %s", tt.name, err) - continue - } - - _, u, err := c.ResolveChartVersion(tt.ref, tt.version) - if err != nil { - t.Errorf("%s: failed with error %s", tt.name, err) - continue - } - - got, err := getter.NewHTTPGetter( - append( - c.Options, - getter.WithURL(u.String()), - )..., - ) - if err != nil { - t.Errorf("%s: failed to create http client: %s", tt.name, err) - continue - } - - if *(got.(*getter.HTTPGetter)) != *(expect.(*getter.HTTPGetter)) { - t.Errorf("%s: expected %s, got %s", tt.name, expect, got) - } + t.Run(tt.name, func(t *testing.T) { + // reset chart downloader options for each test case + c.Options = snapshotOpts + + expect, err := getter.NewHTTPGetter(tt.expect...) + require.NoError(t, err, "failed to setup http client") + + _, u, err := c.ResolveChartVersion(tt.ref, tt.version) + require.NoError(t, err, "failed with error") + + got, err := getter.NewHTTPGetter( + append( + c.Options, + getter.WithURL(u.String()), + )..., + ) + require.NoError(t, err, "failed to create http client") + assert.Equal(t, expect, got) + }) } } diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 4d46d2692..405925a30 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -567,17 +567,16 @@ func TestUpdate(t *testing.T) { // Special handling for the rollback test case if name == "rollback after failed upgrade with removed resource" { - assert.Empty(t, result.Created, "expected 0 resource created, got %d", len(result.Created)) - assert.Len(t, result.Updated, 1, "expected 1 resource updated, got %d", len(result.Updated)) - assert.Empty(t, result.Deleted, "expected 0 resource deleted, got %d", len(result.Deleted)) + assert.Empty(t, result.Created, "expected 0 resource created") + assert.Len(t, result.Updated, 1, "expected 1 resource updated") + assert.Empty(t, result.Deleted, "expected 0 resource deleted") } else { - assert.Len(t, result.Created, 1, "expected 1 resource created, got %d", len(result.Created)) - assert.Len(t, result.Updated, 2, "expected 2 resource updated, got %d", len(result.Updated)) - assert.Len(t, result.Deleted, 1, "expected 1 resource deleted, got %d", len(result.Deleted)) + assert.Len(t, result.Created, 1, "expected 1 resource created") + assert.Len(t, result.Updated, 2, "expected 2 resource updated") + assert.Len(t, result.Deleted, 1, "expected 1 resource deleted") } if tc.ExpectedError != "" { - require.Error(t, err) require.ErrorContains(t, err, tc.ExpectedError) } else { require.NoError(t, err) @@ -702,11 +701,11 @@ func TestPerform(t *testing.T) { } err = perform(infos, fn) - assert.Equal(t, tt.err, (err != nil), "expected error: %v", tt.err) - if err != nil { - require.EqualErrorf(t, err, tt.errMessage, "expected error message: %v, got %v", tt.errMessage, err) + if tt.err { + require.EqualError(t, err, tt.errMessage) + } else { + require.NoError(t, err) } - assert.Len(t, results, tt.count, "expected %d result objects, got %d", tt.count, len(results)) }) } @@ -782,7 +781,6 @@ func TestWait(t *testing.T) { require.NoError(t, err) assert.Len(t, result.Created, 3, "expected 3 resource created, got %d", len(result.Created)) require.NoErrorf(t, c.Wait(resources, time.Second*30), "expected wait without error") - assert.GreaterOrEqualf(t, time.Since(*created), time.Second*5, "expected to wait at least 5 seconds before ready status was detected, but got %s", time.Since(*created)) } diff --git a/pkg/strvals/literal_parser_test.go b/pkg/strvals/literal_parser_test.go index 6c22a732b..94e0c6050 100644 --- a/pkg/strvals/literal_parser_test.go +++ b/pkg/strvals/literal_parser_test.go @@ -16,7 +16,6 @@ limitations under the License. package strvals import ( - "bytes" "fmt" "strings" "testing" @@ -279,29 +278,21 @@ func TestParseLiteral(t *testing.T) { } for _, tt := range cases { - got, err := ParseLiteral(tt.str) - if err != nil { - if !tt.err { - require.NoError(t, err, tt.str) - } - continue - } - - if tt.err { - assert.Fail(t, "Expected error. Got nil", tt.str) - } - - y1, err := yaml.Marshal(tt.expect) - require.NoError(t, err) + t.Run(tt.str, func(t *testing.T) { + got, err := ParseLiteral(tt.str) + if tt.err { + require.Error(t, err) + } else { + require.NoError(t, err) + y1, err := yaml.Marshal(tt.expect) + require.NoError(t, err) - y2, err := yaml.Marshal(got) - if err != nil { - require.NoError(t, err, "Error serializing parsed value") - } + y2, err := yaml.Marshal(got) + require.NoError(t, err, "Error serializing parsed value") - if !bytes.Equal(y1, y2) { - assert.Equal(t, string(y1), string(y2), tt.str) - } + assert.YAMLEq(t, string(y1), string(y2), tt.str) + } + }) } } @@ -386,17 +377,13 @@ func TestParseLiteralInto(t *testing.T) { } for _, tt := range tests { - if err := ParseLiteralInto(tt.input, tt.got); err != nil { - require.NoError(t, err) - } + require.NoError(t, ParseLiteralInto(tt.input, tt.got)) if tt.err { assert.Fail(t, "Expected error. Got nil", tt.input) } if tt.input2 != "" { - if err := ParseLiteralInto(tt.input2, tt.got); err != nil { - require.NoError(t, err) - } + require.NoError(t, ParseLiteralInto(tt.input2, tt.got)) if tt.err { assert.Fail(t, "Expected error. Got nil", tt.input2) } @@ -406,13 +393,9 @@ func TestParseLiteralInto(t *testing.T) { require.NoError(t, err) y2, err := yaml.Marshal(tt.got) - if err != nil { - require.NoError(t, err, "Error serializing parsed value") - } + require.NoError(t, err, "Error serializing parsed value") - if !bytes.Equal(y1, y2) { - assert.Equal(t, string(y1), string(y2), tt.input) - } + assert.YAMLEq(t, string(y1), string(y2), tt.input) } } @@ -447,29 +430,23 @@ func TestParseLiteralNestedLevels(t *testing.T) { } for _, tt := range tests { - got, err := ParseLiteral(tt.str) - if err != nil { + t.Run(tt.str, func(t *testing.T) { + got, err := ParseLiteral(tt.str) if tt.err { + require.Error(t, err) if tt.errStr != "" { require.EqualError(t, err, tt.errStr) } - continue - } - require.NoError(t, err, tt.str) - } - - if tt.err { - assert.Fail(t, "Expected error. Got nil", tt.str) - } - - y1, err := yaml.Marshal(tt.expect) - require.NoError(t, err) + } else { + require.NoError(t, err) + y1, err := yaml.Marshal(tt.expect) + require.NoError(t, err) - y2, err := yaml.Marshal(got) - require.NoError(t, err, "Error serializing parsed value") + y2, err := yaml.Marshal(got) + require.NoError(t, err, "Error serializing parsed value") - if !bytes.Equal(y1, y2) { - assert.Equal(t, string(y1), string(y2), tt.str) - } + assert.YAMLEq(t, string(y1), string(y2), tt.str) + } + }) } } diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go index e68bc4e19..c6c866711 100644 --- a/pkg/strvals/parser_test.go +++ b/pkg/strvals/parser_test.go @@ -16,8 +16,8 @@ limitations under the License. package strvals import ( - "bytes" "fmt" + "strconv" "strings" "testing" @@ -383,50 +383,37 @@ func TestParseSet(t *testing.T) { } for _, tt := range tests { - got, err := Parse(tt.str) - if err != nil { + t.Run(tt.str, func(t *testing.T) { + got, err := Parse(tt.str) if tt.err { - continue - } - require.NoError(t, err, tt.str) - } - if tt.err { - assert.Fail(t, "Expected error. Got nil", tt.str) - } - - y1, err := yaml.Marshal(tt.expect) - require.NoError(t, err) - y2, err := yaml.Marshal(got) - if err != nil { - require.NoError(t, err, "Error serializing parsed value") - } + require.Error(t, err) + } else { + require.NoError(t, err) + y1, err := yaml.Marshal(tt.expect) + require.NoError(t, err) + y2, err := yaml.Marshal(got) + if err != nil { + require.NoError(t, err, "Error serializing parsed value") + } - if !bytes.Equal(y1, y2) { - assert.Equal(t, string(y1), string(y2), tt.str) - } + assert.YAMLEq(t, string(y1), string(y2), tt.str) + } + }) } for _, tt := range testsString { - got, err := ParseString(tt.str) - if err != nil { + t.Run(tt.str, func(t *testing.T) { + got, err := ParseString(tt.str) if tt.err { - continue + require.Error(t, err) + } else { + require.NoError(t, err) + y1, err := yaml.Marshal(tt.expect) + require.NoError(t, err) + y2, err := yaml.Marshal(got) + require.NoError(t, err, "Error serializing parsed value") + assert.YAMLEq(t, string(y1), string(y2)) } - require.NoError(t, err, tt.str) - } - if tt.err { - assert.Fail(t, "Expected error. Got nil", tt.str) - } - - y1, err := yaml.Marshal(tt.expect) - require.NoError(t, err) - y2, err := yaml.Marshal(got) - if err != nil { - require.NoError(t, err, "Error serializing parsed value") - } - - if !bytes.Equal(y1, y2) { - assert.Equal(t, string(y1), string(y2), tt.str) - } + }) } } @@ -511,33 +498,26 @@ func TestParseInto(t *testing.T) { err: false, }, } - for _, tt := range tests { - if err := ParseInto(tt.input, tt.got); err != nil { - require.NoError(t, err) - } - if tt.err { - assert.Fail(t, "Expected error. Got nil", tt.input) - } - - if tt.input2 != "" { - if err := ParseInto(tt.input2, tt.got); err != nil { - require.NoError(t, err) - } + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + require.NoError(t, ParseInto(tt.input, tt.got)) if tt.err { - assert.Fail(t, "Expected error. Got nil", tt.input2) + assert.Fail(t, "Expected error. Got nil", tt.input) } - } - y1, err := yaml.Marshal(tt.expect) - require.NoError(t, err) - y2, err := yaml.Marshal(tt.got) - if err != nil { - require.NoError(t, err, "Error serializing parsed value") - } + if tt.input2 != "" { + require.NoError(t, ParseInto(tt.input2, tt.got)) + if tt.err { + assert.Fail(t, "Expected error. Got nil", tt.input2) + } + } - if !bytes.Equal(y1, y2) { - assert.Equal(t, string(y1), string(y2), tt.input) - } + y1, err := yaml.Marshal(tt.expect) + require.NoError(t, err) + y2, err := yaml.Marshal(tt.got) + require.NoError(t, err, "Error serializing parsed value") + assert.YAMLEq(t, string(y1), string(y2), tt.input) + }) } } @@ -564,13 +544,8 @@ func TestParseIntoString(t *testing.T) { y1, err := yaml.Marshal(expect) require.NoError(t, err) y2, err := yaml.Marshal(got) - if err != nil { - require.NoError(t, err, "Error serializing parsed value") - } - - if !bytes.Equal(y1, y2) { - assert.Equal(t, string(y1), string(y2), input) - } + require.NoError(t, err, "Error serializing parsed value") + assert.YAMLEq(t, string(y1), string(y2), input) } func TestParseJSON(t *testing.T) { @@ -650,27 +625,19 @@ func TestParseJSON(t *testing.T) { }, } for _, tt := range tests { - if err := ParseJSON(tt.input, tt.got); err != nil { + t.Run(tt.input, func(t *testing.T) { + err := ParseJSON(tt.input, tt.got) if tt.err { - continue + require.Error(t, err) + } else { + require.NoError(t, err) + y1, err := yaml.Marshal(tt.expect) + require.NoError(t, err, "Error serializing expected value") + y2, err := yaml.Marshal(tt.got) + require.NoError(t, err, "Error serializing parsed value") + assert.YAMLEq(t, string(y1), string(y2)) } - require.NoError(t, err, tt.input) - } - if tt.err { - require.Fail(t, "Expected error. Got nil", tt.input) - } - y1, err := yaml.Marshal(tt.expect) - if err != nil { - require.NoError(t, err, "Error serializing expected value") - } - y2, err := yaml.Marshal(tt.got) - if err != nil { - require.NoError(t, err, "Error serializing parsed value") - } - - if !bytes.Equal(y1, y2) { - assert.Equal(t, string(y1), string(y2), tt.input) - } + }) } } @@ -693,14 +660,10 @@ func TestParseFile(t *testing.T) { y1, err := yaml.Marshal(expect) require.NoError(t, err) - y2, err := yaml.Marshal(got) - if err != nil { - require.NoError(t, err, "Error serializing parsed value") - } - if !bytes.Equal(y1, y2) { - assert.Equal(t, string(y1), string(y2), input) - } + y2, err := yaml.Marshal(got) + require.NoError(t, err, "Error serializing parsed value") + assert.YAMLEq(t, string(y1), string(y2), input) } func TestParseIntoFile(t *testing.T) { @@ -725,13 +688,9 @@ func TestParseIntoFile(t *testing.T) { y1, err := yaml.Marshal(expect) require.NoError(t, err) y2, err := yaml.Marshal(got) - if err != nil { - require.NoError(t, err, "Error serializing parsed value") - } + require.NoError(t, err, "Error serializing parsed value") - if !bytes.Equal(y1, y2) { - assert.Equal(t, string(y1), string(y2), input) - } + assert.YAMLEq(t, string(y1), string(y2), input) } func TestToYAML(t *testing.T) { @@ -775,27 +734,23 @@ func TestParseSetNestedLevels(t *testing.T) { } for _, tt := range tests { - got, err := Parse(tt.str) - if err != nil { + t.Run(tt.str, func(t *testing.T) { + got, err := Parse(tt.str) if tt.err { + require.Error(t, err) if tt.errStr != "" { require.EqualError(t, err, tt.errStr) } - continue - } - require.NoError(t, err, tt.str) - } - if tt.err { - assert.Fail(t, "Expected error. Got nil", tt.str) - } + } else { + require.NoError(t, err) - y1, err := yaml.Marshal(tt.expect) - require.NoError(t, err) - y2, err := yaml.Marshal(got) - require.NoError(t, err, "Error serializing parsed value") + y1, err := yaml.Marshal(tt.expect) + require.NoError(t, err) - if !bytes.Equal(y1, y2) { - assert.Equal(t, string(y1), string(y2), tt.str) - } + y2, err := yaml.Marshal(got) + require.NoError(t, err, "Error serializing parsed value") + assert.YAMLEq(t, string(y1), string(y2), tt.str) + } + }) } } From a27f3a062027d8b459eed2186076fb9384b82f38 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 24 Jul 2026 18:19:56 +0200 Subject: [PATCH 493/571] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Matthieu MOREL --- pkg/cmd/rollback_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/rollback_test.go b/pkg/cmd/rollback_test.go index aa60043aa..e4dbf9779 100644 --- a/pkg/cmd/rollback_test.go +++ b/pkg/cmd/rollback_test.go @@ -160,5 +160,5 @@ func TestRollbackWithLabels(t *testing.T) { updatedRel, err := releaserToV1Release(updatedReli) require.NoError(t, err) - assert.Equalf(t, updatedRel.Labels, labels1, "Expected {%v}, got {%v}", labels1, updatedRel.Labels) +assert.Equalf(t, labels1, updatedRel.Labels, "Expected {%v}, got {%v}", labels1, updatedRel.Labels) } From b59dfd39015311d94c04541ec5b91959c6b4b6d3 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 24 Jul 2026 18:44:53 +0200 Subject: [PATCH 494/571] Update rollback_test.go Signed-off-by: Matthieu MOREL --- pkg/cmd/rollback_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/rollback_test.go b/pkg/cmd/rollback_test.go index e4dbf9779..21d6a65af 100644 --- a/pkg/cmd/rollback_test.go +++ b/pkg/cmd/rollback_test.go @@ -160,5 +160,5 @@ func TestRollbackWithLabels(t *testing.T) { updatedRel, err := releaserToV1Release(updatedReli) require.NoError(t, err) -assert.Equalf(t, labels1, updatedRel.Labels, "Expected {%v}, got {%v}", labels1, updatedRel.Labels) + assert.Equalf(t, labels1, updatedRel.Labels, "Expected {%v}, got {%v}", labels1, updatedRel.Labels) } From bfca0688b1a44b1cf7ae37ac5f64792a8dffe7f1 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 24 Jul 2026 18:45:58 +0200 Subject: [PATCH 495/571] Update show_test.go Signed-off-by: Matthieu MOREL --- pkg/cmd/show_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/show_test.go b/pkg/cmd/show_test.go index b7ad94d94..61c4f8b10 100644 --- a/pkg/cmd/show_test.go +++ b/pkg/cmd/show_test.go @@ -78,7 +78,7 @@ func TestShowPreReleaseChart(t *testing.T) { ) _, _, err := executeActionCommand(cmd) if tt.fail { - assert.ErrorContains(t, err, tt.expectedErr, "%q expected error: %s, got: %s", tt.name, tt.expectedErr, err.Error()) + assert.ErrorContains(t, err, tt.expectedErr) } else { assert.NoError(t, err) } From 532eabf88afa2b119f90ef82d1bf309923cbbb78 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 24 Jul 2026 18:47:43 +0200 Subject: [PATCH 496/571] Update chartrepo_test.go Signed-off-by: Matthieu MOREL --- pkg/repo/v1/chartrepo_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/repo/v1/chartrepo_test.go b/pkg/repo/v1/chartrepo_test.go index 54275e28d..f0e5839ac 100644 --- a/pkg/repo/v1/chartrepo_test.go +++ b/pkg/repo/v1/chartrepo_test.go @@ -175,6 +175,7 @@ func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) { // versions of Darwin do not. As there are people developing Helm using both old and new versions of Darwin we test // for both messages. if runtime.GOOS == "darwin" { + require.Error(t, err) assert.True(t, strings.Contains(err.Error(), "x509: “Acme Co” certificate is not trusted") || strings.Contains(err.Error(), "x509: certificate signed by unknown authority"), "Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err) } else { assert.ErrorContainsf(t, err, "x509: certificate signed by unknown authority", "Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error") From 61d99a8bdbc90219c474c96e11790ab6a90cde61 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 24 Jul 2026 18:53:45 +0200 Subject: [PATCH 497/571] Update labels_test.go Signed-off-by: Matthieu MOREL --- pkg/storage/driver/labels_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/storage/driver/labels_test.go b/pkg/storage/driver/labels_test.go index 481b8c467..34f01be79 100644 --- a/pkg/storage/driver/labels_test.go +++ b/pkg/storage/driver/labels_test.go @@ -45,7 +45,11 @@ func TestLabelsMatch(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - require.False(t, !tt.set1.match(tt.set2) && tt.expect) + if tt.expect { + require.True(t, tt.set1.match(tt.set2)) + } else { + require.False(t, tt.set1.match(tt.set2)) + } }) } } From 622f16b37fdb1f1915aece9436165dc53c26b3c5 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 24 Jul 2026 19:06:05 +0200 Subject: [PATCH 498/571] Update resource_test.go Signed-off-by: Matthieu MOREL --- pkg/kube/resource_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/kube/resource_test.go b/pkg/kube/resource_test.go index 18501d4e8..8bc644833 100644 --- a/pkg/kube/resource_test.go +++ b/pkg/kube/resource_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/cli-runtime/pkg/resource" @@ -41,11 +42,11 @@ func TestResourceList(t *testing.T) { assert.Equal(t, "pod", r1.Get(info("bar")).Mapping.Resource.Resource, "expected get pod") diff := r1.Difference(r2) - assert.Len(t, diff, 1, "expected 1 result") + require.Len(t, diff, 1, "expected 1 result") assert.Contains(t, diff, info("foo"), "expected diff to return foo") inter := r1.Intersect(r2) - assert.Len(t, inter, 1, "expected 1 result") + require.Len(t, inter, 1, "expected 1 result") assert.Contains(t, inter, info("bar"), "expected intersect to return bar") } From f8180e6de473bb4b1008b76aa2ac63b8bcd104c0 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:06:31 +0100 Subject: [PATCH 499/571] feat(rollback): add --description flag to provide rollback reason Add a new --description flag to the helm rollback command that allows users to specify a custom description explaining why the rollback was performed. This description is stored in the release metadata. Changes: - Add Description field to the Rollback action struct - Add --description flag to the rollback CLI command - Add 512 character limit validation for the description - Default to 'Rollback to ' when no description is provided The description flag is optional and follows the same pattern used by the install and upgrade commands. Closes #XXXX Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/action/rollback.go | 10 +- pkg/action/rollback_test.go | 135 +++++++++++++++++- pkg/cmd/rollback.go | 9 ++ pkg/cmd/rollback_test.go | 84 +++++++++++ .../output/rollback-with-description.txt | 2 + 5 files changed, 236 insertions(+), 4 deletions(-) create mode 100644 pkg/cmd/testdata/output/rollback-with-description.txt diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 6fc449c30..a4eb09643 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -59,6 +59,8 @@ type Rollback struct { ServerSideApply string CleanupOnFail bool MaxHistory int // MaxHistory limits the maximum number of revisions saved per release + // Description is the description of this rollback operation + Description string } // NewRollback creates a new Rollback object with the given configuration. @@ -169,6 +171,12 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele return nil, nil, false, err } + // Determine the description for this rollback + description := fmt.Sprintf("Rollback to %d", previousVersion) + if r.Description != "" { + description = r.Description + } + // Store a new release object with previous release's configuration targetRelease := &release.Release{ Name: name, @@ -183,7 +191,7 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele RollbackRevision: previousVersion, // Because we lose the reference to previous version elsewhere, we set the // message here, and only override it later if we experience failure. - Description: fmt.Sprintf("Rollback to %d", previousVersion), + Description: description, }, Version: currentRelease.Version + 1, Labels: previousRelease.Labels, diff --git a/pkg/action/rollback_test.go b/pkg/action/rollback_test.go index 7ffd90d25..f85d1a690 100644 --- a/pkg/action/rollback_test.go +++ b/pkg/action/rollback_test.go @@ -27,14 +27,26 @@ import ( "helm.sh/helm/v4/pkg/kube" kubefake "helm.sh/helm/v4/pkg/kube/fake" + "helm.sh/helm/v4/pkg/release/common" ) +func rollbackAction(t *testing.T) *Rollback { + t.Helper() + config := actionConfigFixture(t) + rollAction := NewRollback(config) + return rollAction +} + func TestNewRollback(t *testing.T) { + is := assert.New(t) config := actionConfigFixture(t) - client := NewRollback(config) - assert.NotNil(t, client) - assert.Equal(t, config, client.cfg) + rollback := NewRollback(config) + + is.NotNil(rollback) + is.Equal(config, rollback.cfg) + is.Equal(DryRunNone, rollback.DryRunStrategy) + is.Empty(rollback.Description) } func TestRollbackRun_UnreachableKubeClient(t *testing.T) { @@ -131,3 +143,120 @@ func TestRollbackRevisionZeroForNonRollback(t *testing.T) { assert.Equal(t, 0, r.Info.RollbackRevision) } + +func TestRollback_WithDescription(t *testing.T) { + is := assert.New(t) + req := require.New(t) + + rollAction := rollbackAction(t) + + // Create two releases - version 1 (superseded) and version 2 (deployed) + rel1 := releaseStub() + rel1.Name = "test-release" + rel1.Version = 1 + rel1.Info.Status = common.StatusSuperseded + rel1.ApplyMethod = "csa" // client-side apply + req.NoError(rollAction.cfg.Releases.Create(rel1)) + + rel2 := releaseStub() + rel2.Name = "test-release" + rel2.Version = 2 + rel2.Info.Status = common.StatusDeployed + rel2.ApplyMethod = "csa" // client-side apply + req.NoError(rollAction.cfg.Releases.Create(rel2)) + + // Set custom description + customDescription := "Rollback due to critical bug in version 2" + rollAction.Description = customDescription + rollAction.Version = 1 + rollAction.ServerSideApply = "false" // Disable server-side apply for testing + + err := rollAction.Run("test-release") + req.NoError(err) + + // Get the new release (version 3) + newReleasei, err := rollAction.cfg.Releases.Get("test-release", 3) + req.NoError(err) + newRelease, err := releaserToV1Release(newReleasei) + req.NoError(err) + + // Verify the custom description was set + is.Equal(customDescription, newRelease.Info.Description) +} + +func TestRollback_DefaultDescription(t *testing.T) { + is := assert.New(t) + req := require.New(t) + + rollAction := rollbackAction(t) + + // Create two releases - version 1 (superseded) and version 2 (deployed) + rel1 := releaseStub() + rel1.Name = "test-release-default" + rel1.Version = 1 + rel1.Info.Status = common.StatusSuperseded + rel1.ApplyMethod = "csa" // client-side apply + req.NoError(rollAction.cfg.Releases.Create(rel1)) + + rel2 := releaseStub() + rel2.Name = "test-release-default" + rel2.Version = 2 + rel2.Info.Status = common.StatusDeployed + rel2.ApplyMethod = "csa" // client-side apply + req.NoError(rollAction.cfg.Releases.Create(rel2)) + + // Don't set a description, rely on default + rollAction.Version = 1 + rollAction.ServerSideApply = "false" // Disable server-side apply for testing + + err := rollAction.Run("test-release-default") + req.NoError(err) + + // Get the new release (version 3) + newReleasei, err := rollAction.cfg.Releases.Get("test-release-default", 3) + req.NoError(err) + newRelease, err := releaserToV1Release(newReleasei) + req.NoError(err) + + // Verify the default description was set + is.Equal("Rollback to 1", newRelease.Info.Description) +} + +func TestRollback_EmptyDescription(t *testing.T) { + is := assert.New(t) + req := require.New(t) + + rollAction := rollbackAction(t) + + // Create two releases - version 1 (superseded) and version 2 (deployed) + rel1 := releaseStub() + rel1.Name = "test-release-empty" + rel1.Version = 1 + rel1.Info.Status = common.StatusSuperseded + rel1.ApplyMethod = "csa" // client-side apply + req.NoError(rollAction.cfg.Releases.Create(rel1)) + + rel2 := releaseStub() + rel2.Name = "test-release-empty" + rel2.Version = 2 + rel2.Info.Status = common.StatusDeployed + rel2.ApplyMethod = "csa" // client-side apply + req.NoError(rollAction.cfg.Releases.Create(rel2)) + + // Set empty description (should use default) + rollAction.Description = "" + rollAction.Version = 1 + rollAction.ServerSideApply = "false" // Disable server-side apply for testing + + err := rollAction.Run("test-release-empty") + req.NoError(err) + + // Get the new release (version 3) + newReleasei, err := rollAction.cfg.Releases.Get("test-release-empty", 3) + req.NoError(err) + newRelease, err := releaserToV1Release(newReleasei) + req.NoError(err) + + // Verify the default description was used for empty string + is.Equal("Rollback to 1", newRelease.Info.Description) +} diff --git a/pkg/cmd/rollback.go b/pkg/cmd/rollback.go index b716dae10..fb2b80c5c 100644 --- a/pkg/cmd/rollback.go +++ b/pkg/cmd/rollback.go @@ -38,6 +38,9 @@ second is a revision (version) number. If this argument is omitted or set to To see revision numbers, run 'helm history RELEASE'. ` +// maxDescriptionLength is the maximum length allowed for a rollback description +const maxDescriptionLength = 512 + func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewRollback(cfg) client.WaitOptions = append(client.WaitOptions, defaultCLIWaitOptions()...) @@ -67,6 +70,11 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.Version = ver } + // Validate description length + if len(client.Description) > maxDescriptionLength { + return fmt.Errorf("description must be %d characters or less, got %d", maxDescriptionLength, len(client.Description)) + } + dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, false) if err != nil { return err @@ -83,6 +91,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } f := cmd.Flags() + f.StringVar(&client.Description, "description", "", fmt.Sprintf("add a custom description for the rollback (max %d characters)", maxDescriptionLength)) f.BoolVar(&client.ForceReplace, "force-replace", false, "force resource updates by replacement") f.BoolVar(&client.ForceReplace, "force", false, "deprecated") f.MarkDeprecated("force", "use --force-replace instead") diff --git a/pkg/cmd/rollback_test.go b/pkg/cmd/rollback_test.go index 21d6a65af..f607f57b1 100644 --- a/pkg/cmd/rollback_test.go +++ b/pkg/cmd/rollback_test.go @@ -18,6 +18,7 @@ package cmd import ( "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -81,6 +82,11 @@ func TestRollbackCmd(t *testing.T) { golden: "output/rollback-no-args.txt", rels: rels, wantError: true, + }, { + name: "rollback a release with description", + cmd: "rollback funny-honey 1 --description 'Reverting due to bug in version 2'", + golden: "output/rollback-with-description.txt", + rels: rels, }} runTestCmd(t, tests) } @@ -127,6 +133,84 @@ func TestRollbackFileCompletion(t *testing.T) { checkFileCompletion(t, "rollback myrelease 1", false) } +func TestRollbackWithDescription(t *testing.T) { + releaseName := "funny-bunny-desc" + rels := []*release.Release{ + { + Name: releaseName, + Info: &release.Info{Status: common.StatusSuperseded}, + Chart: &chart.Chart{}, + Version: 1, + }, + { + Name: releaseName, + Info: &release.Info{Status: common.StatusDeployed}, + Chart: &chart.Chart{}, + Version: 2, + }, + } + storage := storageFixture() + for _, rel := range rels { + if err := storage.Create(rel); err != nil { + t.Fatal(err) + } + } + + customDescription := "Rollback due to critical bug in version 2" + _, _, err := executeActionCommandC(storage, fmt.Sprintf("rollback %s 1 --description '%s'", releaseName, customDescription)) + if err != nil { + t.Fatalf("unexpected error, got '%v'", err) + } + + // Verify the description was stored correctly + updatedReli, err := storage.Get(releaseName, 3) + if err != nil { + t.Fatalf("unexpected error getting release, got '%v'", err) + } + updatedRel, err := releaserToV1Release(updatedReli) + if err != nil { + t.Fatalf("unexpected error converting release, got '%v'", err) + } + + if updatedRel.Info.Description != customDescription { + t.Errorf("Expected description '%s', got '%s'", customDescription, updatedRel.Info.Description) + } +} + +func TestRollbackDescriptionTooLong(t *testing.T) { + releaseName := "funny-bunny-long-desc" + rels := []*release.Release{ + { + Name: releaseName, + Info: &release.Info{Status: common.StatusSuperseded}, + Chart: &chart.Chart{}, + Version: 1, + }, + { + Name: releaseName, + Info: &release.Info{Status: common.StatusDeployed}, + Chart: &chart.Chart{}, + Version: 2, + }, + } + storage := storageFixture() + for _, rel := range rels { + if err := storage.Create(rel); err != nil { + t.Fatal(err) + } + } + + // Create a description that exceeds the 512 character limit + longDescription := strings.Repeat("a", 513) + _, _, err := executeActionCommandC(storage, fmt.Sprintf("rollback %s 1 --description '%s'", releaseName, longDescription)) + if err == nil { + t.Error("expected error for description exceeding max length, got success") + } + if err != nil && !strings.Contains(err.Error(), "description must be 512 characters or less") { + t.Errorf("expected error about description length, got: %v", err) + } +} + func TestRollbackWithLabels(t *testing.T) { labels1 := map[string]string{"operation": "install", "firstLabel": "firstValue"} labels2 := map[string]string{"operation": "upgrade", "secondLabel": "secondValue"} diff --git a/pkg/cmd/testdata/output/rollback-with-description.txt b/pkg/cmd/testdata/output/rollback-with-description.txt new file mode 100644 index 000000000..a034dd2df --- /dev/null +++ b/pkg/cmd/testdata/output/rollback-with-description.txt @@ -0,0 +1,2 @@ +Rollback was a success! Happy Helming! + From b606955f095408270914c47eac8414b9061bfb49 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Mon, 16 Feb 2026 13:50:56 +0100 Subject: [PATCH 500/571] Set 256 as limit for maxDescriptionLength. 256 comes from MAX value for key and value Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/cmd/rollback.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/rollback.go b/pkg/cmd/rollback.go index fb2b80c5c..35257a779 100644 --- a/pkg/cmd/rollback.go +++ b/pkg/cmd/rollback.go @@ -39,7 +39,7 @@ To see revision numbers, run 'helm history RELEASE'. ` // maxDescriptionLength is the maximum length allowed for a rollback description -const maxDescriptionLength = 512 +const maxDescriptionLength = 256 func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewRollback(cfg) From e69e424ab1917f0d53eafe63c408b5280b39624a Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Tue, 17 Feb 2026 13:17:34 +0100 Subject: [PATCH 501/571] Changed if construction Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/action/rollback.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index a4eb09643..5f4c5a8a9 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -172,9 +172,9 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele } // Determine the description for this rollback - description := fmt.Sprintf("Rollback to %d", previousVersion) - if r.Description != "" { - description = r.Description + description := r.Description + if description == "" { + description = fmt.Sprintf("Rollback to %d", previousVersion) } // Store a new release object with previous release's configuration From cb91040c447723a7338a513a6d1604e5daaa1e0b Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Mon, 23 Feb 2026 08:40:44 +0100 Subject: [PATCH 502/571] Enhanced rollback description length Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/action/rollback.go | 6 ++++ pkg/action/rollback_test.go | 64 +++++++++++++++++++++++++++++++++++++ pkg/cmd/rollback.go | 9 ++---- pkg/cmd/rollback_test.go | 5 ++- 4 files changed, 75 insertions(+), 9 deletions(-) diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 5f4c5a8a9..93a7ab194 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -31,6 +31,8 @@ import ( "helm.sh/helm/v4/pkg/storage/driver" ) +const MaxDescriptionLength = 256 + // Rollback is the action for rolling back to a given release. // // It provides the implementation of 'helm rollback'. @@ -118,6 +120,10 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele return nil, nil, false, errInvalidRevision } + if len(r.Description) > MaxDescriptionLength { + return nil, nil, false, fmt.Errorf("description must be %d characters or less, got %d", MaxDescriptionLength, len(r.Description)) + } + currentReleasei, err := r.cfg.Releases.Last(name) if err != nil { return nil, nil, false, err diff --git a/pkg/action/rollback_test.go b/pkg/action/rollback_test.go index f85d1a690..1d483a17e 100644 --- a/pkg/action/rollback_test.go +++ b/pkg/action/rollback_test.go @@ -20,6 +20,7 @@ import ( "context" "errors" "io" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -260,3 +261,66 @@ func TestRollback_EmptyDescription(t *testing.T) { // Verify the default description was used for empty string is.Equal("Rollback to 1", newRelease.Info.Description) } + +func TestRollback_DescriptionTooLong(t *testing.T) { + req := require.New(t) + + rollAction := rollbackAction(t) + + rel1 := releaseStub() + rel1.Name = "test-release-desc-long" + rel1.Version = 1 + rel1.Info.Status = common.StatusSuperseded + rel1.ApplyMethod = "csa" + req.NoError(rollAction.cfg.Releases.Create(rel1)) + + rel2 := releaseStub() + rel2.Name = "test-release-desc-long" + rel2.Version = 2 + rel2.Info.Status = common.StatusDeployed + rel2.ApplyMethod = "csa" + req.NoError(rollAction.cfg.Releases.Create(rel2)) + + rollAction.Description = strings.Repeat("a", MaxDescriptionLength+1) + rollAction.Version = 1 + rollAction.ServerSideApply = "false" + + err := rollAction.Run("test-release-desc-long") + req.Error(err) + req.Contains(err.Error(), "description must be") +} + +func TestRollback_DescriptionAtMaxLength(t *testing.T) { + is := assert.New(t) + req := require.New(t) + + rollAction := rollbackAction(t) + + rel1 := releaseStub() + rel1.Name = "test-release-desc-max" + rel1.Version = 1 + rel1.Info.Status = common.StatusSuperseded + rel1.ApplyMethod = "csa" + req.NoError(rollAction.cfg.Releases.Create(rel1)) + + rel2 := releaseStub() + rel2.Name = "test-release-desc-max" + rel2.Version = 2 + rel2.Info.Status = common.StatusDeployed + rel2.ApplyMethod = "csa" + req.NoError(rollAction.cfg.Releases.Create(rel2)) + + rollAction.Description = strings.Repeat("a", MaxDescriptionLength) + rollAction.Version = 1 + rollAction.ServerSideApply = "false" + + err := rollAction.Run("test-release-desc-max") + req.NoError(err) + + newReleasei, err := rollAction.cfg.Releases.Get("test-release-desc-max", 3) + req.NoError(err) + newRelease, err := releaserToV1Release(newReleasei) + req.NoError(err) + + is.Equal(strings.Repeat("a", MaxDescriptionLength), newRelease.Info.Description) +} diff --git a/pkg/cmd/rollback.go b/pkg/cmd/rollback.go index 35257a779..1fbd04fc8 100644 --- a/pkg/cmd/rollback.go +++ b/pkg/cmd/rollback.go @@ -38,9 +38,6 @@ second is a revision (version) number. If this argument is omitted or set to To see revision numbers, run 'helm history RELEASE'. ` -// maxDescriptionLength is the maximum length allowed for a rollback description -const maxDescriptionLength = 256 - func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewRollback(cfg) client.WaitOptions = append(client.WaitOptions, defaultCLIWaitOptions()...) @@ -71,8 +68,8 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } // Validate description length - if len(client.Description) > maxDescriptionLength { - return fmt.Errorf("description must be %d characters or less, got %d", maxDescriptionLength, len(client.Description)) + if len(client.Description) > action.MaxDescriptionLength { + return fmt.Errorf("description must be %d characters or less, got %d", action.MaxDescriptionLength, len(client.Description)) } dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, false) @@ -91,7 +88,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } f := cmd.Flags() - f.StringVar(&client.Description, "description", "", fmt.Sprintf("add a custom description for the rollback (max %d characters)", maxDescriptionLength)) + f.StringVar(&client.Description, "description", "", fmt.Sprintf("add a custom description for the rollback (max %d characters)", action.MaxDescriptionLength)) f.BoolVar(&client.ForceReplace, "force-replace", false, "force resource updates by replacement") f.BoolVar(&client.ForceReplace, "force", false, "deprecated") f.MarkDeprecated("force", "use --force-replace instead") diff --git a/pkg/cmd/rollback_test.go b/pkg/cmd/rollback_test.go index f607f57b1..44119d9e7 100644 --- a/pkg/cmd/rollback_test.go +++ b/pkg/cmd/rollback_test.go @@ -200,13 +200,12 @@ func TestRollbackDescriptionTooLong(t *testing.T) { } } - // Create a description that exceeds the 512 character limit - longDescription := strings.Repeat("a", 513) + longDescription := strings.Repeat("a", action.MaxDescriptionLength+1) _, _, err := executeActionCommandC(storage, fmt.Sprintf("rollback %s 1 --description '%s'", releaseName, longDescription)) if err == nil { t.Error("expected error for description exceeding max length, got success") } - if err != nil && !strings.Contains(err.Error(), "description must be 512 characters or less") { + if err != nil && !strings.Contains(err.Error(), fmt.Sprintf("description must be %d characters or less", action.MaxDescriptionLength)) { t.Errorf("expected error about description length, got: %v", err) } } From 8a34f3b7b4d9ffa4e82c7893eb3d8572fd464198 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Mon, 23 Feb 2026 08:45:48 +0100 Subject: [PATCH 503/571] Use rune count to count properly multi-byte UTF-8 characters Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/action/rollback.go | 5 +++-- pkg/action/rollback_test.go | 36 ++++++++++++++++++++++++++++++++++++ pkg/cmd/rollback.go | 5 +++-- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 93a7ab194..497842c3c 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "time" + "unicode/utf8" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -120,8 +121,8 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele return nil, nil, false, errInvalidRevision } - if len(r.Description) > MaxDescriptionLength { - return nil, nil, false, fmt.Errorf("description must be %d characters or less, got %d", MaxDescriptionLength, len(r.Description)) + if utf8.RuneCountInString(r.Description) > MaxDescriptionLength { + return nil, nil, false, fmt.Errorf("description must be %d characters or less, got %d", MaxDescriptionLength, utf8.RuneCountInString(r.Description)) } currentReleasei, err := r.cfg.Releases.Last(name) diff --git a/pkg/action/rollback_test.go b/pkg/action/rollback_test.go index 1d483a17e..af7fe6107 100644 --- a/pkg/action/rollback_test.go +++ b/pkg/action/rollback_test.go @@ -324,3 +324,39 @@ func TestRollback_DescriptionAtMaxLength(t *testing.T) { is.Equal(strings.Repeat("a", MaxDescriptionLength), newRelease.Info.Description) } + +func TestRollback_DescriptionMultiByteCharacters(t *testing.T) { + is := assert.New(t) + req := require.New(t) + + rollAction := rollbackAction(t) + + rel1 := releaseStub() + rel1.Name = "test-release-desc-utf8" + rel1.Version = 1 + rel1.Info.Status = common.StatusSuperseded + rel1.ApplyMethod = "csa" + req.NoError(rollAction.cfg.Releases.Create(rel1)) + + rel2 := releaseStub() + rel2.Name = "test-release-desc-utf8" + rel2.Version = 2 + rel2.Info.Status = common.StatusDeployed + rel2.ApplyMethod = "csa" + req.NoError(rollAction.cfg.Releases.Create(rel2)) + + // "é" is 2 bytes in UTF-8 but 1 rune + rollAction.Description = strings.Repeat("é", MaxDescriptionLength) + rollAction.Version = 1 + rollAction.ServerSideApply = "false" + + err := rollAction.Run("test-release-desc-utf8") + req.NoError(err) + + newReleasei, err := rollAction.cfg.Releases.Get("test-release-desc-utf8", 3) + req.NoError(err) + newRelease, err := releaserToV1Release(newReleasei) + req.NoError(err) + + is.Equal(strings.Repeat("é", MaxDescriptionLength), newRelease.Info.Description) +} diff --git a/pkg/cmd/rollback.go b/pkg/cmd/rollback.go index 1fbd04fc8..bad1e3068 100644 --- a/pkg/cmd/rollback.go +++ b/pkg/cmd/rollback.go @@ -21,6 +21,7 @@ import ( "io" "strconv" "time" + "unicode/utf8" "github.com/spf13/cobra" @@ -68,8 +69,8 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } // Validate description length - if len(client.Description) > action.MaxDescriptionLength { - return fmt.Errorf("description must be %d characters or less, got %d", action.MaxDescriptionLength, len(client.Description)) + if utf8.RuneCountInString(client.Description) > action.MaxDescriptionLength { + return fmt.Errorf("description must be %d characters or less, got %d", action.MaxDescriptionLength, utf8.RuneCountInString(client.Description)) } dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, false) From a92611c20241be8e7dc32f9b229afd2a722a06f8 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:07:13 +0100 Subject: [PATCH 504/571] Enhance rune count in pkg/cmd/rollback.go and pkg/action/rollback.go Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/action/rollback.go | 4 ++-- pkg/cmd/rollback.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 497842c3c..9a5526e94 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -121,8 +121,8 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele return nil, nil, false, errInvalidRevision } - if utf8.RuneCountInString(r.Description) > MaxDescriptionLength { - return nil, nil, false, fmt.Errorf("description must be %d characters or less, got %d", MaxDescriptionLength, utf8.RuneCountInString(r.Description)) + if descLen := utf8.RuneCountInString(r.Description); descLen > MaxDescriptionLength { + return nil, nil, false, fmt.Errorf("description must be %d characters or less, got %d", MaxDescriptionLength, descLen) } currentReleasei, err := r.cfg.Releases.Last(name) diff --git a/pkg/cmd/rollback.go b/pkg/cmd/rollback.go index bad1e3068..62d0be4af 100644 --- a/pkg/cmd/rollback.go +++ b/pkg/cmd/rollback.go @@ -69,8 +69,8 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } // Validate description length - if utf8.RuneCountInString(client.Description) > action.MaxDescriptionLength { - return fmt.Errorf("description must be %d characters or less, got %d", action.MaxDescriptionLength, utf8.RuneCountInString(client.Description)) + if descLen := utf8.RuneCountInString(client.Description); descLen > action.MaxDescriptionLength { + return fmt.Errorf("description must be %d characters or less, got %d", action.MaxDescriptionLength, descLen) } dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, false) From b177a8e9060720e91c402cdba2224c6d58789fa3 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:45:01 +0100 Subject: [PATCH 505/571] Removed extra space in rollback-with-description.txt Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/cmd/testdata/output/rollback-with-description.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/cmd/testdata/output/rollback-with-description.txt b/pkg/cmd/testdata/output/rollback-with-description.txt index a034dd2df..ae3c6f1c4 100644 --- a/pkg/cmd/testdata/output/rollback-with-description.txt +++ b/pkg/cmd/testdata/output/rollback-with-description.txt @@ -1,2 +1 @@ Rollback was a success! Happy Helming! - From e73dfab5fb47c9f3a0f24cb3f3a7a4aa8b65143e Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:29:30 +0100 Subject: [PATCH 506/571] refactor(test): reuse existing rollback.txt golden file Remove duplicate rollback-with-description.txt fixture that had identical content to rollback.txt. Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/cmd/rollback_test.go | 2 +- pkg/cmd/testdata/output/rollback-with-description.txt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 pkg/cmd/testdata/output/rollback-with-description.txt diff --git a/pkg/cmd/rollback_test.go b/pkg/cmd/rollback_test.go index 44119d9e7..2e523a5f6 100644 --- a/pkg/cmd/rollback_test.go +++ b/pkg/cmd/rollback_test.go @@ -85,7 +85,7 @@ func TestRollbackCmd(t *testing.T) { }, { name: "rollback a release with description", cmd: "rollback funny-honey 1 --description 'Reverting due to bug in version 2'", - golden: "output/rollback-with-description.txt", + golden: "output/rollback.txt", rels: rels, }} runTestCmd(t, tests) diff --git a/pkg/cmd/testdata/output/rollback-with-description.txt b/pkg/cmd/testdata/output/rollback-with-description.txt deleted file mode 100644 index ae3c6f1c4..000000000 --- a/pkg/cmd/testdata/output/rollback-with-description.txt +++ /dev/null @@ -1 +0,0 @@ -Rollback was a success! Happy Helming! From 91520a78063582ab8265d9364815665f688deebc Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:32:41 +0100 Subject: [PATCH 507/571] refactor(rollback): validate description length before cluster reachability check Move description length validation to the beginning of Run(), before IsReachable(), so programmatic callers get an immediate validation error instead of a potentially misleading cluster reachability failure. Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/action/rollback.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 9a5526e94..52fdfbbf8 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -77,6 +77,10 @@ func NewRollback(cfg *Configuration) *Rollback { // Run executes 'helm rollback' against the given release. func (r *Rollback) Run(name string) error { + if descLen := utf8.RuneCountInString(r.Description); descLen > MaxDescriptionLength { + return fmt.Errorf("description must be %d characters or less, got %d", MaxDescriptionLength, descLen) + } + if err := r.cfg.KubeClient.IsReachable(); err != nil { return err } @@ -121,10 +125,6 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele return nil, nil, false, errInvalidRevision } - if descLen := utf8.RuneCountInString(r.Description); descLen > MaxDescriptionLength { - return nil, nil, false, fmt.Errorf("description must be %d characters or less, got %d", MaxDescriptionLength, descLen) - } - currentReleasei, err := r.cfg.Releases.Last(name) if err != nil { return nil, nil, false, err From 4fcc4e4b3db363654f3dd5240211b195e309970e Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Mon, 22 Jun 2026 08:25:49 +0200 Subject: [PATCH 508/571] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/action/rollback.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 52fdfbbf8..b24b4a83d 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -32,6 +32,8 @@ import ( "helm.sh/helm/v4/pkg/storage/driver" ) +// MaxDescriptionLength is the maximum length allowed for a rollback description, +// including values provided via the --description flag and Rollback.Description. const MaxDescriptionLength = 256 // Rollback is the action for rolling back to a given release. From f1ede7c9c114c745da2f5c6cef8d27e1eb342498 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Tue, 23 Jun 2026 10:58:37 +0200 Subject: [PATCH 509/571] Potential fix for pull request finding If the CLI-side description validation is removed, the unicode/utf8 import becomes unused and should also be dropped. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/cmd/rollback.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/cmd/rollback.go b/pkg/cmd/rollback.go index 62d0be4af..12f4b6b3c 100644 --- a/pkg/cmd/rollback.go +++ b/pkg/cmd/rollback.go @@ -21,7 +21,6 @@ import ( "io" "strconv" "time" - "unicode/utf8" "github.com/spf13/cobra" From 500f02d597628d8ca7be8b530edcbedbf397b676 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:22:55 +0200 Subject: [PATCH 510/571] Readded "unicode/utf8" Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/cmd/rollback.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/cmd/rollback.go b/pkg/cmd/rollback.go index 12f4b6b3c..62d0be4af 100644 --- a/pkg/cmd/rollback.go +++ b/pkg/cmd/rollback.go @@ -21,6 +21,7 @@ import ( "io" "strconv" "time" + "unicode/utf8" "github.com/spf13/cobra" From 94a3315f4d699c6bb4140550d4c98b46bb4382d2 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Mon, 27 Jul 2026 09:41:29 +0200 Subject: [PATCH 511/571] Added missing import Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/cmd/rollback_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/cmd/rollback_test.go b/pkg/cmd/rollback_test.go index 2e523a5f6..00b314a4e 100644 --- a/pkg/cmd/rollback_test.go +++ b/pkg/cmd/rollback_test.go @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "helm.sh/helm/v4/pkg/action" chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/release/common" From f3edb83df48c505878ffd3ebb89ef6a809e14008 Mon Sep 17 00:00:00 2001 From: MrJack <36191829+biagiopietro@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:46:55 +0200 Subject: [PATCH 512/571] Make golangci-lint happy Signed-off-by: MrJack <36191829+biagiopietro@users.noreply.github.com> --- pkg/cmd/rollback_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/rollback_test.go b/pkg/cmd/rollback_test.go index 00b314a4e..1a617b228 100644 --- a/pkg/cmd/rollback_test.go +++ b/pkg/cmd/rollback_test.go @@ -23,8 +23,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/action" chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/release/common" release "helm.sh/helm/v4/pkg/release/v1" From bbaf420a101379dad4f8452129cf26f2fff7e536 Mon Sep 17 00:00:00 2001 From: Solomon Wakhungu <65043605+1solomonwakhungu@users.noreply.github.com> Date: Mon, 27 Jul 2026 10:42:43 -0500 Subject: [PATCH 513/571] fix(scripts): address cache-busting review feedback Signed-off-by: Solomon Wakhungu <65043605+1solomonwakhungu@users.noreply.github.com> --- scripts/get-helm-3 | 4 ++-- scripts/get-helm-4 | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/get-helm-3 b/scripts/get-helm-3 index dad66ab20..117ae3e03 100755 --- a/scripts/get-helm-3 +++ b/scripts/get-helm-3 @@ -114,8 +114,8 @@ verifySupported() { checkDesiredVersion() { if [ "x$DESIRED_VERSION" == "x" ]; then # Get tag from release URL - # Append a cache-busting query string to avoid CDN edge servers - # serving stale version files after a new release. + # The current CDN does not revalidate on the request no-cache directive, + # so use a unique query while retaining no-cache for compliant intermediaries. local latest_release_url="https://get.helm.sh/helm3-latest-version?ts=$(date +%s)" local latest_release_response="" if [ "${HAS_CURL}" == "true" ]; then diff --git a/scripts/get-helm-4 b/scripts/get-helm-4 index 1c90bbad5..0ff312a7f 100644 --- a/scripts/get-helm-4 +++ b/scripts/get-helm-4 @@ -114,12 +114,14 @@ verifySupported() { checkDesiredVersion() { if [ "x$DESIRED_VERSION" == "x" ]; then # Get tag from release URL - local latest_release_url="https://get.helm.sh/helm4-latest-version" + # The current CDN does not revalidate on the request no-cache directive, + # so use a unique query while retaining no-cache for compliant intermediaries. + local latest_release_url="https://get.helm.sh/helm4-latest-version?ts=$(date +%s)" local latest_release_response="" if [ "${HAS_CURL}" == "true" ]; then - latest_release_response=$( curl -L --silent --show-error --fail "$latest_release_url" 2>&1 || true ) + latest_release_response=$( curl -L --silent --show-error --fail -H "Cache-Control: no-cache" "$latest_release_url" 2>&1 || true ) elif [ "${HAS_WGET}" == "true" ]; then - latest_release_response=$( wget "$latest_release_url" -q -O - 2>&1 || true ) + latest_release_response=$( wget "$latest_release_url" --header="Cache-Control: no-cache" -q -O - 2>&1 || true ) fi TAG=$( echo "$latest_release_response" | grep '^v[0-9]' ) if [ "x$TAG" == "x" ]; then From 24bbb46c62a54000394ce77c4e3fda17d5566ffc Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 27 Jul 2026 19:53:24 +0200 Subject: [PATCH 514/571] chore: fix gofumpt issues Signed-off-by: Matthieu MOREL --- .golangci.yml | 5 + internal/chart/v3/lint/lint_test.go | 18 ++- .../chart/v3/lint/rules/chartfile_test.go | 22 +-- internal/chart/v3/lint/rules/template_test.go | 17 +- .../chart/v3/lint/support/message_test.go | 2 +- internal/monocular/client.go | 1 - .../plugin/installer/http_installer_test.go | 4 +- .../plugin/installer/vcs_installer_test.go | 1 + internal/plugin/loader.go | 1 - internal/plugin/metadata.go | 3 +- internal/plugin/verify_test.go | 6 +- internal/release/v2/release.go | 6 +- .../release/v2/util/manifest_sorter_test.go | 15 +- internal/third_party/dep/fs/fs_test.go | 12 +- internal/tlsutil/tls.go | 3 +- pkg/action/get_metadata_test.go | 2 +- pkg/action/hooks.go | 12 +- pkg/action/hooks_test.go | 10 +- pkg/action/install.go | 1 - pkg/action/install_test.go | 4 + pkg/action/list.go | 3 +- pkg/action/release_testing.go | 1 - pkg/action/rollback.go | 1 - pkg/action/uninstall.go | 1 - pkg/action/uninstall_test.go | 1 + pkg/chart/common.go | 8 +- pkg/chart/common/capabilities.go | 1 - pkg/chart/v2/lint/lint_test.go | 18 ++- pkg/chart/v2/lint/rules/chartfile.go | 1 - pkg/chart/v2/lint/rules/chartfile_test.go | 26 +-- pkg/chart/v2/lint/rules/template_test.go | 11 +- pkg/chart/v2/lint/support/message_test.go | 2 +- pkg/chart/v2/loader/load_test.go | 3 +- pkg/cmd/completion.go | 1 + pkg/cmd/dependency_build_test.go | 3 - pkg/cmd/dependency_test.go | 12 +- pkg/cmd/dependency_update_test.go | 1 - pkg/cmd/flags.go | 1 - pkg/cmd/get_hooks.go | 1 - pkg/cmd/get_manifest.go | 1 - pkg/cmd/get_metadata.go | 1 - pkg/cmd/get_notes.go | 1 - pkg/cmd/get_values.go | 1 - pkg/cmd/load_plugins.go | 1 - pkg/cmd/pull.go | 1 - pkg/cmd/pull_test.go | 3 +- pkg/cmd/push.go | 1 - pkg/cmd/repo_remove_test.go | 4 +- pkg/cmd/repo_update_test.go | 2 +- pkg/cmd/root.go | 3 - pkg/cmd/search_hub_test.go | 10 +- pkg/cmd/search_repo_test.go | 3 +- pkg/cmd/show.go | 1 - pkg/cmd/status_test.go | 3 +- pkg/cmd/template.go | 1 - pkg/engine/engine.go | 8 +- pkg/engine/engine_test.go | 1 + pkg/engine/funcs.go | 1 - pkg/engine/funcs_test.go | 152 +++++++++--------- pkg/getter/ocigetter.go | 1 - pkg/getter/plugingetter.go | 1 - pkg/getter/plugingetter_test.go | 1 - pkg/kube/client.go | 6 +- pkg/kube/client_test.go | 3 +- pkg/kube/converter.go | 8 +- pkg/provenance/sign.go | 1 - pkg/registry/client.go | 4 +- pkg/registry/client_scope_test.go | 1 + pkg/release/common.go | 4 +- pkg/release/v1/release.go | 6 +- pkg/release/v1/util/manifest_sorter_test.go | 15 +- pkg/repo/v1/index_test.go | 2 +- pkg/repo/v1/repo_test.go | 10 +- pkg/storage/driver/labels_test.go | 2 +- pkg/storage/driver/memory_test.go | 10 +- pkg/storage/driver/records_test.go | 12 +- pkg/storage/driver/sql.go | 21 +-- pkg/storage/driver/util_test.go | 4 +- pkg/storage/storage_test.go | 9 +- pkg/strvals/literal_parser_test.go | 3 +- pkg/strvals/parser_test.go | 3 +- 81 files changed, 307 insertions(+), 260 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 5b6d7cf8e..9daa85ad5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,7 @@ formatters: enable: - gofmt + - gofumpt - goimports exclusions: @@ -10,6 +11,10 @@ formatters: gofmt: simplify: true + gofumpt: + module-path: helm.sh/helm/v4 + extra-rules: false + goimports: local-prefixes: - helm.sh/helm/v4 diff --git a/internal/chart/v3/lint/lint_test.go b/internal/chart/v3/lint/lint_test.go index b0a7ce651..5eb185f07 100644 --- a/internal/chart/v3/lint/lint_test.go +++ b/internal/chart/v3/lint/lint_test.go @@ -30,14 +30,16 @@ import ( const namespace = "testNamespace" -const badChartDir = "rules/testdata/badchartfile" -const badValuesFileDir = "rules/testdata/badvaluesfile" -const badYamlFileDir = "rules/testdata/albatross" -const badCrdFileDir = "rules/testdata/badcrdfile" -const goodChartDir = "rules/testdata/goodone" -const subChartValuesDir = "rules/testdata/withsubchart" -const malformedTemplate = "rules/testdata/malformed-template" -const invalidChartFileDir = "rules/testdata/invalidchartfile" +const ( + badChartDir = "rules/testdata/badchartfile" + badValuesFileDir = "rules/testdata/badvaluesfile" + badYamlFileDir = "rules/testdata/albatross" + badCrdFileDir = "rules/testdata/badcrdfile" + goodChartDir = "rules/testdata/goodone" + subChartValuesDir = "rules/testdata/withsubchart" + malformedTemplate = "rules/testdata/malformed-template" + invalidChartFileDir = "rules/testdata/invalidchartfile" +) func TestBadChartV3(t *testing.T) { var values map[string]any diff --git a/internal/chart/v3/lint/rules/chartfile_test.go b/internal/chart/v3/lint/rules/chartfile_test.go index 0638589b6..e06d7bdc4 100644 --- a/internal/chart/v3/lint/rules/chartfile_test.go +++ b/internal/chart/v3/lint/rules/chartfile_test.go @@ -44,8 +44,10 @@ var ( nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml") ) -var badChart, _ = chartutil.LoadChartfile(badChartFilePath) -var badChartName, _ = chartutil.LoadChartfile(badChartNamePath) +var ( + badChart, _ = chartutil.LoadChartfile(badChartFilePath) + badChartName, _ = chartutil.LoadChartfile(badChartNamePath) +) // Validation functions Test func TestValidateChartYamlNotDirectory(t *testing.T) { @@ -68,7 +70,7 @@ func TestValidateChartName(t *testing.T) { } func TestValidateChartVersion(t *testing.T) { - var failTest = []struct { + failTest := []struct { Version string ErrorMsg string }{ @@ -80,7 +82,7 @@ func TestValidateChartVersion(t *testing.T) { {"1", "'1' is not a valid SemVerV2"}, } - var successTest = []string{"0.0.1", "0.0.1+build", "0.0.1-beta"} + successTest := []string{"0.0.1", "0.0.1+build", "0.0.1-beta"} for i, test := range failTest { t.Run(strconv.Itoa(i), func(t *testing.T) { @@ -96,7 +98,7 @@ func TestValidateChartVersion(t *testing.T) { } func TestValidateChartMaintainer(t *testing.T) { - var failTest = []struct { + failTest := []struct { Name string Email string ErrorMsg string @@ -106,7 +108,7 @@ func TestValidateChartMaintainer(t *testing.T) { {"John Snow", "wrongFormatEmail.com", "invalid email"}, } - var successTest = []struct { + successTest := []struct { Name string Email string }{ @@ -134,8 +136,8 @@ func TestValidateChartMaintainer(t *testing.T) { } func TestValidateChartSources(t *testing.T) { - var failTest = []string{"", "RiverRun", "john@winterfell", "riverrun.io"} - var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"} + failTest := []string{"", "RiverRun", "john@winterfell", "riverrun.io"} + successTest := []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"} for _, test := range failTest { t.Run(test, func(t *testing.T) { badChart.Sources = []string{test} @@ -166,8 +168,8 @@ func TestValidateChartIconPresence(t *testing.T) { } func TestValidateChartIconURL(t *testing.T) { - var failTest = []string{"RiverRun", "john@winterfell", "riverrun.io"} - var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish.png"} + failTest := []string{"RiverRun", "john@winterfell", "riverrun.io"} + successTest := []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish.png"} for _, test := range failTest { t.Run(test, func(t *testing.T) { badChart.Icon = test diff --git a/internal/chart/v3/lint/rules/template_test.go b/internal/chart/v3/lint/rules/template_test.go index 3271a5bbe..4a1e15f55 100644 --- a/internal/chart/v3/lint/rules/template_test.go +++ b/internal/chart/v3/lint/rules/template_test.go @@ -35,11 +35,11 @@ import ( const templateTestBasedir = "./testdata/albatross" func TestValidateAllowedExtension(t *testing.T) { - var failTest = []string{"/foo", "/test.toml"} + failTest := []string{"/foo", "/test.toml"} for _, test := range failTest { require.ErrorContains(t, validateAllowedExtension(test), "Valid extensions are .yaml, .yml, .tpl, or .txt", "validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test) } - var successTest = []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"} + successTest := []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"} for _, test := range successTest { assert.NoError(t, validateAllowedExtension(test), "validateAllowedExtension('%s') to return no error", test) } @@ -47,8 +47,10 @@ func TestValidateAllowedExtension(t *testing.T) { var values = map[string]any{"nameOverride": "", "httpPort": 80} -const namespace = "testNamespace" -const strict = false +const ( + namespace = "testNamespace" + strict = false +) func TestTemplateParsing(t *testing.T) { linter := support.Linter{ChartDir: templateTestBasedir} @@ -59,8 +61,10 @@ func TestTemplateParsing(t *testing.T) { assert.ErrorContains(t, res[0].Err, "deliberateSyntaxError") } -var wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml") -var ignoredTemplatePath = filepath.Join(templateTestBasedir, "fail.yaml.ignored") +var ( + wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml") + ignoredTemplatePath = filepath.Join(templateTestBasedir, "fail.yaml.ignored") +) // Test a template with all the existing features: // namespaces, partial templates @@ -382,6 +386,7 @@ func TestEmptyWithCommentsManifests(t *testing.T) { } require.Empty(t, linter.Messages, "Expected 0 lint errors") } + func TestValidateListAnnotations(t *testing.T) { md := &k8sYamlStruct{ APIVersion: "v1", diff --git a/internal/chart/v3/lint/support/message_test.go b/internal/chart/v3/lint/support/message_test.go index d07cf9ed4..6edd9261f 100644 --- a/internal/chart/v3/lint/support/message_test.go +++ b/internal/chart/v3/lint/support/message_test.go @@ -27,7 +27,7 @@ import ( var errLint = errors.New("lint failed") func TestRunLinterRule(t *testing.T) { - var tests = []struct { + tests := []struct { Severity int LintError error ExpectedMessages int diff --git a/internal/monocular/client.go b/internal/monocular/client.go index cd1a0d666..a6e6be0d4 100644 --- a/internal/monocular/client.go +++ b/internal/monocular/client.go @@ -26,7 +26,6 @@ var ErrHostnameNotProvided = errors.New("no hostname provided") // Client represents a client capable of communicating with the Monocular API. type Client struct { - // The base URL for requests BaseURL string } diff --git a/internal/plugin/installer/http_installer_test.go b/internal/plugin/installer/http_installer_test.go index df8385ed6..3ae813b14 100644 --- a/internal/plugin/installer/http_installer_test.go +++ b/internal/plugin/installer/http_installer_test.go @@ -173,7 +173,7 @@ func TestExtract(t *testing.T) { // Write a tarball to a buffer for us to extract var tarbuf bytes.Buffer tw := tar.NewWriter(&tarbuf) - var files = []struct { + files := []struct { Name, Body string Mode int64 }{ @@ -293,7 +293,7 @@ func TestExtractWithNestedDirectories(t *testing.T) { // Write a tarball with nested directory structure var tarbuf bytes.Buffer tw := tar.NewWriter(&tarbuf) - var files = []struct { + files := []struct { Name string Body string Mode int64 diff --git a/internal/plugin/installer/vcs_installer_test.go b/internal/plugin/installer/vcs_installer_test.go index 82401d069..0fee301a4 100644 --- a/internal/plugin/installer/vcs_installer_test.go +++ b/internal/plugin/installer/vcs_installer_test.go @@ -105,6 +105,7 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) { } require.EqualErrorf(t, err, fmt.Sprintf("requested version %q does not exist for plugin %q", version, source), "expected error for version does not exists") } + func TestVCSInstallerUpdate(t *testing.T) { ensure.HelmHome(t) diff --git a/internal/plugin/loader.go b/internal/plugin/loader.go index cfa618f39..af9e175b4 100644 --- a/internal/plugin/loader.go +++ b/internal/plugin/loader.go @@ -216,7 +216,6 @@ func findPlugins(pluginsDirs []string, findFn findFunc, filterFn filterFunc) ([] var found []Plugin for _, pluginsDir := range pluginsDirs { ps, err := findFn(pluginsDir) - if err != nil { return nil, err } diff --git a/internal/plugin/metadata.go b/internal/plugin/metadata.go index 53d6da4e6..ad42fe98a 100644 --- a/internal/plugin/metadata.go +++ b/internal/plugin/metadata.go @@ -154,8 +154,7 @@ func buildLegacyConfig(m MetadataLegacy, pluginType string) Config { func buildLegacyRuntimeConfig(m MetadataLegacy) RuntimeConfig { var protocolCommands []SubprocessProtocolCommand if len(m.Downloaders) > 0 { - protocolCommands = - make([]SubprocessProtocolCommand, 0, len(m.Downloaders)) + protocolCommands = make([]SubprocessProtocolCommand, 0, len(m.Downloaders)) for _, d := range m.Downloaders { protocolCommands = append(protocolCommands, SubprocessProtocolCommand{ Protocols: d.Protocols, diff --git a/internal/plugin/verify_test.go b/internal/plugin/verify_test.go index 4f5b26a97..ab7238973 100644 --- a/internal/plugin/verify_test.go +++ b/internal/plugin/verify_test.go @@ -26,8 +26,10 @@ import ( "helm.sh/helm/v4/pkg/provenance" ) -const testKeyFile = "../../pkg/cmd/testdata/helm-test-key.secret" -const testPubFile = "../../pkg/cmd/testdata/helm-test-key.pub" +const ( + testKeyFile = "../../pkg/cmd/testdata/helm-test-key.secret" + testPubFile = "../../pkg/cmd/testdata/helm-test-key.pub" +) const testPluginYAML = `apiVersion: v1 name: test-plugin diff --git a/internal/release/v2/release.go b/internal/release/v2/release.go index 8b8f2ee07..d32a1cdc4 100644 --- a/internal/release/v2/release.go +++ b/internal/release/v2/release.go @@ -22,8 +22,10 @@ import ( type ApplyMethod string -const ApplyMethodClientSideApply ApplyMethod = "csa" -const ApplyMethodServerSideApply ApplyMethod = "ssa" +const ( + ApplyMethodClientSideApply ApplyMethod = "csa" + ApplyMethodServerSideApply ApplyMethod = "ssa" +) // Release describes a deployment of a chart, together with the chart // and the variables used to deploy that chart. diff --git a/internal/release/v2/util/manifest_sorter_test.go b/internal/release/v2/util/manifest_sorter_test.go index 6cf529bff..e132ef072 100644 --- a/internal/release/v2/util/manifest_sorter_test.go +++ b/internal/release/v2/util/manifest_sorter_test.go @@ -61,7 +61,8 @@ metadata: annotations: "helm.sh/hook": post-install `, - }, { + }, + { name: []string{"third"}, path: "three", kind: []string{"ReplicaSet"}, @@ -73,7 +74,8 @@ metadata: annotations: "helm.sh/hook": no-such-hook `, - }, { + }, + { name: []string{"fourth"}, path: "four", kind: []string{"Pod"}, @@ -84,7 +86,8 @@ metadata: name: fourth annotations: nothing: here`, - }, { + }, + { name: []string{"fifth"}, path: "five", kind: []string{"ReplicaSet"}, @@ -96,14 +99,16 @@ metadata: annotations: "helm.sh/hook": post-delete, post-install `, - }, { + }, + { // Regression test: files with an underscore in the base name should be skipped. name: []string{"sixth"}, path: "six/_six", kind: []string{"ReplicaSet"}, hooks: map[string][]release.HookEvent{"sixth": nil}, manifest: `invalid manifest`, // This will fail if partial is not skipped. - }, { + }, + { // Regression test: files with no content should be skipped. name: []string{"seventh"}, path: "seven", diff --git a/internal/third_party/dep/fs/fs_test.go b/internal/third_party/dep/fs/fs_test.go index d777b6af4..eb910d6c7 100644 --- a/internal/third_party/dep/fs/fs_test.go +++ b/internal/third_party/dep/fs/fs_test.go @@ -126,7 +126,7 @@ func TestCopyDirFail_SrcInaccessible(t *testing.T) { t.Skip("skipping on windows") } - var currentUID = os.Getuid() + currentUID := os.Getuid() if currentUID == 0 { // Skipping if root, because all files are accessible @@ -155,7 +155,7 @@ func TestCopyDirFail_DstInaccessible(t *testing.T) { t.Skip("skipping on windows") } - var currentUID = os.Getuid() + currentUID := os.Getuid() if currentUID == 0 { // Skipping if root, because all files are accessible @@ -217,7 +217,7 @@ func TestCopyDirFailOpen(t *testing.T) { t.Skip("skipping on windows") } - var currentUID = os.Getuid() + currentUID := os.Getuid() if currentUID == 0 { // Skipping if root, because all files are accessible @@ -319,7 +319,7 @@ func TestCopyFileFail(t *testing.T) { t.Skip("skipping on windows") } - var currentUID = os.Getuid() + currentUID := os.Getuid() if currentUID == 0 { // Skipping if root, because all files are accessible @@ -389,7 +389,7 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { } func TestIsDir(t *testing.T) { - var currentUID = os.Getuid() + currentUID := os.Getuid() if currentUID == 0 { // Skipping if root, because all files are accessible @@ -441,7 +441,7 @@ func TestIsDir(t *testing.T) { } func TestIsSymlink(t *testing.T) { - var currentUID = os.Getuid() + currentUID := os.Getuid() if currentUID == 0 { // Skipping if root, because all files are accessible diff --git a/internal/tlsutil/tls.go b/internal/tlsutil/tls.go index e986e6909..d0123daf5 100644 --- a/internal/tlsutil/tls.go +++ b/internal/tlsutil/tls.go @@ -19,10 +19,9 @@ package tlsutil import ( "crypto/tls" "crypto/x509" + "errors" "fmt" "os" - - "errors" ) type TLSConfigOptions struct { diff --git a/pkg/action/get_metadata_test.go b/pkg/action/get_metadata_test.go index f8851e456..9e32d840c 100644 --- a/pkg/action/get_metadata_test.go +++ b/pkg/action/get_metadata_test.go @@ -546,7 +546,7 @@ func TestMetadata_FormattedDepNames(t *testing.T) { } func convertDeps(deps []*chart.Dependency) []ci.Dependency { - var newDeps = make([]ci.Dependency, len(deps)) + newDeps := make([]ci.Dependency, len(deps)) for i, c := range deps { newDeps[i] = c } diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index d7824d69f..3c434e291 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -34,7 +34,8 @@ import ( // execHook executes all of the hooks for the given hook event. func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, - timeout time.Duration, serverSideApply bool) error { + timeout time.Duration, serverSideApply bool, +) error { shutdown, err := cfg.execHookWithDelayedShutdown(rl, hook, waitStrategy, waitOptions, timeout, serverSideApply) if shutdown == nil { return err @@ -57,7 +58,8 @@ func shutdownNoOp() error { // execHookWithDelayedShutdown executes all of the hooks for the given hook event and returns a shutdownHook function to trigger deletions after doing other things like e.g. retrieving logs. func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration, - serverSideApply bool) (ExecuteShutdownFunc, error) { + serverSideApply bool, +) (ExecuteShutdownFunc, error) { executingHooks := []*release.Hook{} for _, h := range rl.Hooks { @@ -176,7 +178,8 @@ func (x hookByWeight) Less(i, j int) bool { // deleteHookByPolicy deletes a hook if the hook policy instructs it to func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy, - waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error { + waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration, +) error { // Never delete CustomResourceDefinitions; this could cause lots of // cascading garbage collection. if h.Kind == "CustomResourceDefinition" { @@ -210,7 +213,8 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo // deleteHooksByPolicy deletes all hooks if the hook policy instructs it to func (cfg *Configuration) deleteHooksByPolicy(hooks []*release.Hook, policy release.HookDeletePolicy, - waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error { + waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration, +) error { for _, h := range hooks { if err := cfg.deleteHookByPolicy(h, policy, waitStrategy, waitOptions, timeout); err != nil { return err diff --git a/pkg/action/hooks_test.go b/pkg/action/hooks_test.go index ad3a4b618..4cb176d36 100644 --- a/pkg/action/hooks_test.go +++ b/pkg/action/hooks_test.go @@ -252,7 +252,6 @@ func (*HookFailingKubeClient) Build(reader io.Reader, _ bool) (kube.ResourceList configMap := &v1.ConfigMap{} err := yaml.NewYAMLOrJSONDecoder(reader, 1000).Decode(configMap) - if err != nil { return kube.ResourceList{}, err } @@ -358,10 +357,12 @@ data: }, }, }, - }, resource.Info{ + }, + resource.Info{ Name: "build-config-2", Namespace: "test", - }, []resource.Info{ + }, + []resource.Info{ { // This should be in the record for `before-hook-creation` Name: "build-config-1", @@ -382,7 +383,8 @@ data: Name: "build-config-1", Namespace: "test", }, - }, true, + }, + true, }, } diff --git a/pkg/action/install.go b/pkg/action/install.go index d73eca619..605c423bc 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -733,7 +733,6 @@ func writeToFile(outputDir string, name string, data string, appendData bool) er defer f.Close() _, err = fmt.Fprintf(f, "---\n# Source: %s\n%s\n", name, data) - if err != nil { return err } diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 4dbeb7c79..80f3bd724 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -631,6 +631,7 @@ func TestInstallRelease_Wait(t *testing.T) { is.Equal(goroutines, instAction.getGoroutineCount()) } + func TestInstallRelease_Wait_Interrupted(t *testing.T) { is := assert.New(t) req := require.New(t) @@ -655,6 +656,7 @@ func TestInstallRelease_Wait_Interrupted(t *testing.T) { time.Sleep(10 * time.Second) // wait for goroutine to finish is.Equal(goroutines, instAction.getGoroutineCount()) } + func TestInstallRelease_WaitForJobs(t *testing.T) { is := assert.New(t) req := require.New(t) @@ -722,6 +724,7 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) { is.ErrorContains(err, "an error occurred while uninstalling the release") }) } + func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { is := assert.New(t) req := require.New(t) @@ -754,6 +757,7 @@ func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { time.Sleep(10 * time.Second) // wait for goroutine to finish is.Equal(goroutines, instAction.getGoroutineCount()) } + func TestNameTemplate(t *testing.T) { testCases := []nameTemplateTestCase{ // Just a straight up nop please diff --git a/pkg/action/list.go b/pkg/action/list.go index a055f760d..7902837fd 100644 --- a/pkg/action/list.go +++ b/pkg/action/list.go @@ -172,7 +172,6 @@ func (l *List) Run() ([]ri.Releaser, error) { return true }) - if err != nil { return nil, err } @@ -264,7 +263,7 @@ func filterLatestReleases(releases []*release.Release) []*release.Release { latestReleases[key] = rls } - var list = make([]*release.Release, 0, len(latestReleases)) + list := make([]*release.Release, 0, len(latestReleases)) for _, rls := range latestReleases { list = append(list, rls) } diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index 5604e8cc4..6edb644c1 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -107,7 +107,6 @@ func (r *ReleaseTesting) Run(name string) (ri.Releaser, ExecuteShutdownFunc, err serverSideApply := rel.ApplyMethod == string(release.ApplyMethodServerSideApply) shutdown, err := r.cfg.execHookWithDelayedShutdown(rel, release.HookTest, kube.StatusWatcherStrategy, r.WaitOptions, r.Timeout, serverSideApply) - if err != nil { rel.Hooks = append(skippedHooks, rel.Hooks...) r.cfg.Releases.Update(reli) diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 6fc449c30..f2cdaf177 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -232,7 +232,6 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas kube.ClientUpdateOptionServerSideApply(serverSideApply, r.ForceConflicts), kube.ClientUpdateOptionThreeWayMergeForUnstructured(false), kube.ClientUpdateOptionUpgradeClientSideFieldManager(true)) - if err != nil { msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) r.cfg.Logger().Warn(msg) diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index 47b2e47de..4d2c728db 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -77,7 +77,6 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error) if u.DryRun { ri, err := u.cfg.releaseContent(name, 0) - if err != nil { if u.IgnoreNotFound && errors.Is(err, driver.ErrReleaseNotFound) { return nil, nil diff --git a/pkg/action/uninstall_test.go b/pkg/action/uninstall_test.go index f36fe7874..e306935c7 100644 --- a/pkg/action/uninstall_test.go +++ b/pkg/action/uninstall_test.go @@ -59,6 +59,7 @@ func TestUninstallRelease_ignoreNotFound(t *testing.T) { is.Nil(res) is.NoError(err) } + func TestUninstallRelease_deleteRelease(t *testing.T) { is := assert.New(t) req := require.New(t) diff --git a/pkg/chart/common.go b/pkg/chart/common.go index cec2c7091..be68eeb42 100644 --- a/pkg/chart/common.go +++ b/pkg/chart/common.go @@ -85,7 +85,7 @@ func (r *v2Accessor) IsLibraryChart() bool { } func (r *v2Accessor) Dependencies() []Charter { - var deps = make([]Charter, len(r.chrt.Dependencies())) + deps := make([]Charter, len(r.chrt.Dependencies())) for i, c := range r.chrt.Dependencies() { deps[i] = c } @@ -93,7 +93,7 @@ func (r *v2Accessor) Dependencies() []Charter { } func (r *v2Accessor) MetaDependencies() []Dependency { - var deps = make([]Dependency, len(r.chrt.Metadata.Dependencies)) + deps := make([]Dependency, len(r.chrt.Metadata.Dependencies)) for i, c := range r.chrt.Metadata.Dependencies { deps[i] = c } @@ -154,7 +154,7 @@ func (r *v3Accessor) IsLibraryChart() bool { } func (r *v3Accessor) Dependencies() []Charter { - var deps = make([]Charter, len(r.chrt.Dependencies())) + deps := make([]Charter, len(r.chrt.Dependencies())) for i, c := range r.chrt.Dependencies() { deps[i] = c } @@ -162,7 +162,7 @@ func (r *v3Accessor) Dependencies() []Charter { } func (r *v3Accessor) MetaDependencies() []Dependency { - var deps = make([]Dependency, len(r.chrt.Dependencies())) + deps := make([]Dependency, len(r.chrt.Dependencies())) for i, c := range r.chrt.Metadata.Dependencies { deps[i] = c } diff --git a/pkg/chart/common/capabilities.go b/pkg/chart/common/capabilities.go index 16910acaa..a9374a86f 100644 --- a/pkg/chart/common/capabilities.go +++ b/pkg/chart/common/capabilities.go @@ -47,7 +47,6 @@ var ( panic(fmt.Sprintf("failed to create default capabilities: %v", err)) } return caps - }() ) diff --git a/pkg/chart/v2/lint/lint_test.go b/pkg/chart/v2/lint/lint_test.go index c4705872d..8ff88b05c 100644 --- a/pkg/chart/v2/lint/lint_test.go +++ b/pkg/chart/v2/lint/lint_test.go @@ -30,14 +30,16 @@ import ( const namespace = "testNamespace" -const badChartDir = "rules/testdata/badchartfile" -const badValuesFileDir = "rules/testdata/badvaluesfile" -const badYamlFileDir = "rules/testdata/albatross" -const badCrdFileDir = "rules/testdata/badcrdfile" -const goodChartDir = "rules/testdata/goodone" -const subChartValuesDir = "rules/testdata/withsubchart" -const malformedTemplate = "rules/testdata/malformed-template" -const invalidChartFileDir = "rules/testdata/invalidchartfile" +const ( + badChartDir = "rules/testdata/badchartfile" + badValuesFileDir = "rules/testdata/badvaluesfile" + badYamlFileDir = "rules/testdata/albatross" + badCrdFileDir = "rules/testdata/badcrdfile" + goodChartDir = "rules/testdata/goodone" + subChartValuesDir = "rules/testdata/withsubchart" + malformedTemplate = "rules/testdata/malformed-template" + invalidChartFileDir = "rules/testdata/invalidchartfile" +) func TestBadChart(t *testing.T) { var values map[string]any diff --git a/pkg/chart/v2/lint/rules/chartfile.go b/pkg/chart/v2/lint/rules/chartfile.go index f867daa21..ec4083951 100644 --- a/pkg/chart/v2/lint/rules/chartfile.go +++ b/pkg/chart/v2/lint/rules/chartfile.go @@ -161,7 +161,6 @@ func validateChartVersion(cf *chart.Metadata) error { func validateChartVersionStrictSemVerV2(cf *chart.Metadata) error { _, err := semver.StrictNewVersion(cf.Version) - if err != nil { return fmt.Errorf("version '%s' is not a valid SemVerV2", cf.Version) } diff --git a/pkg/chart/v2/lint/rules/chartfile_test.go b/pkg/chart/v2/lint/rules/chartfile_test.go index 1cc331a74..90f5edd5c 100644 --- a/pkg/chart/v2/lint/rules/chartfile_test.go +++ b/pkg/chart/v2/lint/rules/chartfile_test.go @@ -42,8 +42,10 @@ var ( nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml") ) -var badChart, _ = chartutil.LoadChartfile(badChartFilePath) -var badChartName, _ = chartutil.LoadChartfile(badChartNamePath) +var ( + badChart, _ = chartutil.LoadChartfile(badChartFilePath) + badChartName, _ = chartutil.LoadChartfile(badChartNamePath) +) // Validation functions Test func TestValidateChartYamlNotDirectory(t *testing.T) { @@ -63,7 +65,7 @@ func TestValidateChartName(t *testing.T) { } func TestValidateChartVersion(t *testing.T) { - var failTest = []struct { + failTest := []struct { Version string ErrorMsg string }{ @@ -73,7 +75,7 @@ func TestValidateChartVersion(t *testing.T) { {"-3", "'-3' is not a valid SemVer"}, } - var successTest = []string{"0.0.1", "0.0.1+build", "0.0.1-beta"} + successTest := []string{"0.0.1", "0.0.1+build", "0.0.1-beta"} for _, test := range failTest { badChart.Version = test.Version @@ -87,7 +89,7 @@ func TestValidateChartVersion(t *testing.T) { } func TestValidateChartVersionStrictSemVerV2(t *testing.T) { - var failTest = []struct { + failTest := []struct { Version string ErrorMsg string }{ @@ -96,7 +98,7 @@ func TestValidateChartVersionStrictSemVerV2(t *testing.T) { {"1.1", "version '1.1' is not a valid SemVerV2"}, } - var successTest = []string{"1.1.1", "0.0.1+build", "0.0.1-beta"} + successTest := []string{"1.1.1", "0.0.1+build", "0.0.1-beta"} for _, test := range failTest { badChart.Version = test.Version @@ -110,7 +112,7 @@ func TestValidateChartVersionStrictSemVerV2(t *testing.T) { } func TestValidateChartMaintainer(t *testing.T) { - var failTest = []struct { + failTest := []struct { Name string Email string ErrorMsg string @@ -120,7 +122,7 @@ func TestValidateChartMaintainer(t *testing.T) { {"John Snow", "wrongFormatEmail.com", "invalid email"}, } - var successTest = []struct { + successTest := []struct { Name string Email string }{ @@ -144,8 +146,8 @@ func TestValidateChartMaintainer(t *testing.T) { } func TestValidateChartSources(t *testing.T) { - var failTest = []string{"", "RiverRun", "john@winterfell", "riverrun.io"} - var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"} + failTest := []string{"", "RiverRun", "john@winterfell", "riverrun.io"} + successTest := []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"} for _, test := range failTest { badChart.Sources = []string{test} require.ErrorContainsf(t, validateChartSources(badChart), "invalid source URL", "validateChartSources(%s) to return \"invalid source URL\", got no error", test) @@ -174,8 +176,8 @@ func TestValidateChartIconPresence(t *testing.T) { } func TestValidateChartIconURL(t *testing.T) { - var failTest = []string{"RiverRun", "john@winterfell", "riverrun.io"} - var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish.png"} + failTest := []string{"RiverRun", "john@winterfell", "riverrun.io"} + successTest := []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish.png"} for _, test := range failTest { badChart.Icon = test require.ErrorContainsf(t, validateChartIconURL(badChart), "invalid icon URL", "validateChartIconURL(%s) to return \"invalid icon URL\", got no error", test) diff --git a/pkg/chart/v2/lint/rules/template_test.go b/pkg/chart/v2/lint/rules/template_test.go index ab78450b9..3f350557b 100644 --- a/pkg/chart/v2/lint/rules/template_test.go +++ b/pkg/chart/v2/lint/rules/template_test.go @@ -35,11 +35,11 @@ import ( const templateTestBasedir = "./testdata/albatross" func TestValidateAllowedExtension(t *testing.T) { - var failTest = []string{"/foo", "/test.toml"} + failTest := []string{"/foo", "/test.toml"} for _, test := range failTest { require.ErrorContainsf(t, validateAllowedExtension(test), "Valid extensions are .yaml, .yml, .tpl, or .txt", "validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test) } - var successTest = []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"} + successTest := []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"} for _, test := range successTest { assert.NoError(t, validateAllowedExtension(test), "validateAllowedExtension('%s') to return no error", test) } @@ -62,8 +62,10 @@ func TestTemplateParsing(t *testing.T) { assert.ErrorContains(t, res[0].Err, "deliberateSyntaxError") } -var wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml") -var ignoredTemplatePath = filepath.Join(templateTestBasedir, "fail.yaml.ignored") +var ( + wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml") + ignoredTemplatePath = filepath.Join(templateTestBasedir, "fail.yaml.ignored") +) // Test a template with all the existing features: // namespaces, partial templates @@ -406,6 +408,7 @@ func TestEmptyWithCommentsManifests(t *testing.T) { } require.Empty(t, linter.Messages, "Expected 0 lint errors") } + func TestValidateListAnnotations(t *testing.T) { md := &k8sYamlStruct{ APIVersion: "v1", diff --git a/pkg/chart/v2/lint/support/message_test.go b/pkg/chart/v2/lint/support/message_test.go index 6d09df688..8a4783605 100644 --- a/pkg/chart/v2/lint/support/message_test.go +++ b/pkg/chart/v2/lint/support/message_test.go @@ -27,7 +27,7 @@ import ( var errLint = errors.New("lint failed") func TestRunLinterRule(t *testing.T) { - var tests = []struct { + tests := []struct { Severity int LintError error ExpectedMessages int diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index 4d73cb0d6..8bf254321 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -182,7 +182,8 @@ func TestLoadFiles_BadCases(t *testing.T) { Data: []byte(""), }, }, - expectError: "validation: chart.metadata.apiVersion is required"}, + expectError: "validation: chart.metadata.apiVersion is required", + }, } { _, err := LoadFiles(tt.bufferedFiles) require.Error(t, err, "expected error when load illegal files") diff --git a/pkg/cmd/completion.go b/pkg/cmd/completion.go index c02462d9d..5edac8f44 100644 --- a/pkg/cmd/completion.go +++ b/pkg/cmd/completion.go @@ -29,6 +29,7 @@ import ( const completionDesc = ` Generate autocompletion scripts for Helm for the specified shell. ` + const bashCompDesc = ` Generate the autocompletion script for Helm for the bash shell. diff --git a/pkg/cmd/dependency_build_test.go b/pkg/cmd/dependency_build_test.go index 485ae18b3..1680c23a1 100644 --- a/pkg/cmd/dependency_build_test.go +++ b/pkg/cmd/dependency_build_test.go @@ -59,7 +59,6 @@ func TestDependencyBuildCmd(t *testing.T) { cmd := fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s --plain-http", filepath.Join(rootDir, chartname), repoFile, rootDir) _, out, err := executeActionCommand(cmd) - // In the first pass, we basically want the same results as an update. if err != nil { t.Logf("Output: %s", out) @@ -105,7 +104,6 @@ func TestDependencyBuildCmd(t *testing.T) { skipRefreshCmd := fmt.Sprintf("dependency build '%s' --skip-refresh --repository-config %s --repository-cache %s --plain-http", filepath.Join(rootDir, chartname), repoFile, rootDir) _, out, err = executeActionCommand(skipRefreshCmd) - // In this pass, we check --skip-refresh option becomes effective. if err != nil { t.Logf("Output: %s", out) @@ -136,7 +134,6 @@ func TestDependencyBuildCmdWithHelmV2Hash(t *testing.T) { cmd := fmt.Sprintf("dependency build '%s'", chartName) _, out, err := executeActionCommand(cmd) - // Want to make sure the build can verify Helm v2 hash if err != nil { t.Logf("Output: %s", out) diff --git a/pkg/cmd/dependency_test.go b/pkg/cmd/dependency_test.go index d6bcebf1b..48ab78183 100644 --- a/pkg/cmd/dependency_test.go +++ b/pkg/cmd/dependency_test.go @@ -39,16 +39,20 @@ func TestDependencyListCmd(t *testing.T) { noDependencies.golden = "output/dependency-list-no-requirements-windows.txt" } - tests := []cmdTestCase{noSuchChart, - noDependencies, { + tests := []cmdTestCase{ + noSuchChart, + noDependencies, + { name: "Dependencies in chart dir", cmd: "dependency list testdata/testcharts/reqtest", golden: "output/dependency-list.txt", - }, { + }, + { name: "Dependencies in chart archive", cmd: "dependency list testdata/testcharts/reqtest-0.1.0.tgz", golden: "output/dependency-list-archive.txt", - }} + }, + } runTestCmd(t, tests) } diff --git a/pkg/cmd/dependency_update_test.go b/pkg/cmd/dependency_update_test.go index 13eb74532..34575ad4d 100644 --- a/pkg/cmd/dependency_update_test.go +++ b/pkg/cmd/dependency_update_test.go @@ -209,7 +209,6 @@ func TestDependencyUpdateCmd_WithRepoThatWasNotAdded(t *testing.T) { fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --content-cache %s", dir(chartname), dir("repositories.yaml"), dir(), contentCache), ) - if err != nil { t.Logf("Output: %s", out) t.Fatal(err) diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index e59487044..45da90d0e 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -152,7 +152,6 @@ func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) { sort.Strings(formatNames) return formatNames, cobra.ShellCompDirectiveNoFileComp }) - if err != nil { log.Fatal(err) } diff --git a/pkg/cmd/get_hooks.go b/pkg/cmd/get_hooks.go index d344307cb..4d174675f 100644 --- a/pkg/cmd/get_hooks.go +++ b/pkg/cmd/get_hooks.go @@ -75,7 +75,6 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } return nil, cobra.ShellCompDirectiveNoFileComp }) - if err != nil { log.Fatal(err) } diff --git a/pkg/cmd/get_manifest.go b/pkg/cmd/get_manifest.go index 253b011c1..a7873e587 100644 --- a/pkg/cmd/get_manifest.go +++ b/pkg/cmd/get_manifest.go @@ -71,7 +71,6 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command } return nil, cobra.ShellCompDirectiveNoFileComp }) - if err != nil { log.Fatal(err) } diff --git a/pkg/cmd/get_metadata.go b/pkg/cmd/get_metadata.go index c26af646f..ffbe25f99 100644 --- a/pkg/cmd/get_metadata.go +++ b/pkg/cmd/get_metadata.go @@ -66,7 +66,6 @@ func newGetMetadataCmd(cfg *action.Configuration, out io.Writer) *cobra.Command } return nil, cobra.ShellCompDirectiveNoFileComp }) - if err != nil { log.Fatal(err) } diff --git a/pkg/cmd/get_notes.go b/pkg/cmd/get_notes.go index 7f8754f3c..b6172846f 100644 --- a/pkg/cmd/get_notes.go +++ b/pkg/cmd/get_notes.go @@ -70,7 +70,6 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } return nil, cobra.ShellCompDirectiveNoFileComp }) - if err != nil { log.Fatal(err) } diff --git a/pkg/cmd/get_values.go b/pkg/cmd/get_values.go index c6a89b00c..5db5dec3a 100644 --- a/pkg/cmd/get_values.go +++ b/pkg/cmd/get_values.go @@ -69,7 +69,6 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } return nil, cobra.ShellCompDirectiveNoFileComp }) - if err != nil { log.Fatal(err) } diff --git a/pkg/cmd/load_plugins.go b/pkg/cmd/load_plugins.go index dee42f750..0c74ba51d 100644 --- a/pkg/cmd/load_plugins.go +++ b/pkg/cmd/load_plugins.go @@ -221,7 +221,6 @@ type pluginCommand struct { func loadCompletionForPlugin(pluginCmd *cobra.Command, plug plugin.Plugin) { // Parse the yaml file providing the plugin's sub-commands and flags cmds, err := loadFile(plug.Dir() + string(filepath.Separator) + pluginStaticCompletionFile) - if err != nil { // The file could be missing or invalid. No static completion for this plugin. slog.Debug("plugin completion file loading", slog.String("error", err.Error())) diff --git a/pkg/cmd/pull.go b/pkg/cmd/pull.go index d371db4ad..9f5af462b 100644 --- a/pkg/cmd/pull.go +++ b/pkg/cmd/pull.go @@ -97,7 +97,6 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } return compVersionFlag(args[0], toComplete) }) - if err != nil { log.Fatal(err) } diff --git a/pkg/cmd/pull_test.go b/pkg/cmd/pull_test.go index d0371710b..a62d6b1b4 100644 --- a/pkg/cmd/pull_test.go +++ b/pkg/cmd/pull_test.go @@ -268,7 +268,8 @@ func runPullTests(t *testing.T, tests []struct { wantErrorMsg string expectFile string expectDir bool -}, outdir string, additionalFlags string) { +}, outdir string, additionalFlags string, +) { t.Helper() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/cmd/push.go b/pkg/cmd/push.go index f32ce92be..df0bad643 100644 --- a/pkg/cmd/push.go +++ b/pkg/cmd/push.go @@ -73,7 +73,6 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { registryClient, err := newRegistryClient( out, o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSVerify, o.plainHTTP, o.username, o.password, ) - if err != nil { return fmt.Errorf("missing registry client: %w", err) } diff --git a/pkg/cmd/repo_remove_test.go b/pkg/cmd/repo_remove_test.go index 7d252c35d..f58bc91d9 100644 --- a/pkg/cmd/repo_remove_test.go +++ b/pkg/cmd/repo_remove_test.go @@ -76,7 +76,7 @@ func TestRepoRemove(t *testing.T) { assert.Falsef(t, f.Has(testRepoName), "%s was not successfully removed from repositories list", testRepoName) // Test removal of multiple repos in one go - var testRepoNames = []string{"foo", "bar", "baz"} + testRepoNames := []string{"foo", "bar", "baz"} cacheFiles := make(map[string][]string, len(testRepoNames)) // Add test repos @@ -151,7 +151,7 @@ func TestRepoRemoveCompletion(t *testing.T) { repoFile := filepath.Join(rootDir, "repositories.yaml") repoCache := filepath.Join(rootDir, "cache") - var testRepoNames = []string{"foo", "bar", "baz"} + testRepoNames := []string{"foo", "bar", "baz"} // Add test repos for _, repoName := range testRepoNames { diff --git a/pkg/cmd/repo_update_test.go b/pkg/cmd/repo_update_test.go index 70b6dfbef..e8d7ad64a 100644 --- a/pkg/cmd/repo_update_test.go +++ b/pkg/cmd/repo_update_test.go @@ -159,7 +159,7 @@ func TestUpdateChartsFailWithError(t *testing.T) { ) defer ts.Stop() - var invalidURL = ts.URL() + "55" + invalidURL := ts.URL() + "55" r1, err := repo.NewChartRepository(&repo.Entry{ Name: "charts", URL: invalidURL, diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 410a9162c..dbedca026 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -229,7 +229,6 @@ func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, arg } return nil, cobra.ShellCompDirectiveDefault }) - if err != nil { log.Fatal(err) } @@ -253,7 +252,6 @@ func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, arg } return nil, cobra.ShellCompDirectiveNoFileComp }) - if err != nil { log.Fatal(err) } @@ -446,7 +444,6 @@ func newRegistryClientWithTLS( tlsutil.WithCertKeyPairFiles(certFile, keyFile), tlsutil.WithCAFile(caFile), ) - if err != nil { return nil, fmt.Errorf("can't create TLS config for client: %w", err) } diff --git a/pkg/cmd/search_hub_test.go b/pkg/cmd/search_hub_test.go index a1612443a..8bc5e78af 100644 --- a/pkg/cmd/search_hub_test.go +++ b/pkg/cmd/search_hub_test.go @@ -28,7 +28,7 @@ import ( func TestSearchHubCmd(t *testing.T) { // Setup a mock search service - var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` + searchResult := `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprintln(w, searchResult) })) @@ -37,7 +37,7 @@ func TestSearchHubCmd(t *testing.T) { // The expected output has the URL to the mocked search service in it // Trailing spaces are necessary to preserve in "expected" as the uitable package adds // them during printing. - var expected = fmt.Sprintf(`URL CHART VERSION APP VERSION DESCRIPTION + expected := fmt.Sprintf(`URL CHART VERSION APP VERSION DESCRIPTION %s/charts/stable/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend %s/charts/bitnami/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend `, ts.URL, ts.URL) @@ -51,7 +51,7 @@ func TestSearchHubCmd(t *testing.T) { func TestSearchHubListRepoCmd(t *testing.T) { // Setup a mock search service - var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` + searchResult := `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprintln(w, searchResult) })) @@ -60,7 +60,7 @@ func TestSearchHubListRepoCmd(t *testing.T) { // The expected output has the URL to the mocked search service in it // Trailing spaces are necessary to preserve in "expected" as the uitable package adds // them during printing. - var expected = fmt.Sprintf(`URL CHART VERSION APP VERSION DESCRIPTION REPO URL + expected := fmt.Sprintf(`URL CHART VERSION APP VERSION DESCRIPTION REPO URL %s/charts/stable/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend https://charts.helm.sh/stable %s/charts/bitnami/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend https://charts.bitnami.com `, ts.URL, ts.URL) @@ -96,7 +96,7 @@ func TestSearchHubCmd_FailOnNoResponseTests(t *testing.T) { wantErr bool } - var tests = []testCase{ + tests := []testCase{ { name: "Search hub with no results in response", cmd: `search hub maria`, diff --git a/pkg/cmd/search_repo_test.go b/pkg/cmd/search_repo_test.go index e7f104e05..e28984fd8 100644 --- a/pkg/cmd/search_repo_test.go +++ b/pkg/cmd/search_repo_test.go @@ -61,7 +61,8 @@ func TestSearchRepositoriesCmd(t *testing.T) { cmd: "search repo syzygy --fail-on-no-result", golden: "output/search-not-found-error.txt", wantError: true, - }, {name: "search for 'syzygy' with json output and --fail-on-no-result, expect failure for no results", + }, { + name: "search for 'syzygy' with json output and --fail-on-no-result, expect failure for no results", cmd: "search repo syzygy --output json --fail-on-no-result", golden: "output/search-not-found-error.txt", wantError: true, diff --git a/pkg/cmd/show.go b/pkg/cmd/show.go index a92ca75b4..8252d933e 100644 --- a/pkg/cmd/show.go +++ b/pkg/cmd/show.go @@ -205,7 +205,6 @@ func addShowFlags(subCmd *cobra.Command, client *action.Show) { } return compVersionFlag(args[0], toComplete) }) - if err != nil { log.Fatal(err) } diff --git a/pkg/cmd/status_test.go b/pkg/cmd/status_test.go index b96a0d19a..2e1a5579e 100644 --- a/pkg/cmd/status_test.go +++ b/pkg/cmd/status_test.go @@ -185,7 +185,8 @@ func TestStatusCompletion(t *testing.T) { Version: "1.2.3-prerelease", }, }, - }} + }, + } tests := []cmdTestCase{{ name: "completion for status", diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index ccc1db719..29839b871 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -254,7 +254,6 @@ func writeToFile(outputDir string, name string, data string, appendData bool) er defer f.Close() _, err = fmt.Fprintf(f, "---\n# Source: %s\n%s\n", name, data) - if err != nil { return err } diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 1d0affd93..67b1cf47b 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -143,9 +143,11 @@ type renderable struct { basePath string } -const warnStartDelim = "HELM_ERR_START" -const warnEndDelim = "HELM_ERR_END" -const recursionMaxNums = 1000 +const ( + warnStartDelim = "HELM_ERR_START" + warnEndDelim = "HELM_ERR_END" + recursionMaxNums = 1000 +) var warnRegex = regexp.MustCompile(warnStartDelim + `((?s).*)` + warnEndDelim) diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 1f3096366..14faeeb63 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -1278,6 +1278,7 @@ func TestTraceableError_SimpleForm(t *testing.T) { assert.Equal(t, "error calling include", trace.message, "Expected %q, got %q", errString, trace.message) } } + func TestTraceableError_ExecutingForm(t *testing.T) { testStrings := [][]string{ {"function_not_found/templates/secret.yaml:6:11: executing \"function_not_found/templates/secret.yaml\" at : ", "function_not_found/templates/secret.yaml:6:11"}, diff --git a/pkg/engine/funcs.go b/pkg/engine/funcs.go index 431f82f63..04a48cad3 100644 --- a/pkg/engine/funcs.go +++ b/pkg/engine/funcs.go @@ -130,7 +130,6 @@ func toYAMLPretty(v any) string { encoder := goYaml.NewEncoder(&data) encoder.SetIndent(2) err := encoder.Encode(v) - if err != nil { // Swallow errors inside of a template. return "" diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go index beb87b429..03ed64153 100644 --- a/pkg/engine/funcs_test.go +++ b/pkg/engine/funcs_test.go @@ -147,39 +147,40 @@ keyInElement1 = "valueInElement1"`, tpl string expect any vars any - }{{ - tpl: `{{ mustToYaml . }}`, - vars: loopMap, - }, { - tpl: `{{ mustToJson . }}`, - vars: loopMap, - }, { - tpl: `{{ mustToDuration 30 }}`, - expect: `30s`, - vars: nil, - }, { - tpl: `{{ mustToDuration "1m30s" }}`, - expect: `1m30s`, - vars: nil, - }, { - tpl: `{{ mustToDuration "foo" }}`, - vars: nil, - }, { - tpl: `{{ toYaml . }}`, - expect: "", // should return empty string and swallow error - vars: loopMap, - }, { - tpl: `{{ toJson . }}`, - expect: "", // should return empty string and swallow error - vars: loopMap, - }, { - tpl: `{{ mustToToml . }}`, - vars: map[int]string{1: "one"}, // non-string key is invalid in TOML - }, { - tpl: `{{ mustToToml . }}`, - expect: "foo = \"bar\"\n", // should succeed and return TOML string - vars: map[string]string{"foo": "bar"}, - }, + }{ + { + tpl: `{{ mustToYaml . }}`, + vars: loopMap, + }, { + tpl: `{{ mustToJson . }}`, + vars: loopMap, + }, { + tpl: `{{ mustToDuration 30 }}`, + expect: `30s`, + vars: nil, + }, { + tpl: `{{ mustToDuration "1m30s" }}`, + expect: `1m30s`, + vars: nil, + }, { + tpl: `{{ mustToDuration "foo" }}`, + vars: nil, + }, { + tpl: `{{ toYaml . }}`, + expect: "", // should return empty string and swallow error + vars: loopMap, + }, { + tpl: `{{ toJson . }}`, + expect: "", // should return empty string and swallow error + vars: loopMap, + }, { + tpl: `{{ mustToToml . }}`, + vars: map[int]string{1: "one"}, // non-string key is invalid in TOML + }, { + tpl: `{{ mustToToml . }}`, + expect: "foo = \"bar\"\n", // should succeed and return TOML string + vars: map[string]string{"foo": "bar"}, + }, } for _, tt := range mustFuncsTests { @@ -368,48 +369,49 @@ func TestDurationHelpers(t *testing.T) { name string tpl string vars any - }{{ - name: "mustToDuration invalid string", - tpl: `{{ mustToDuration "nope" }}`, - }, { - name: "mustToDuration empty string", - tpl: `{{ mustToDuration "" }}`, - }, { - name: "mustToDuration whitespace string", - tpl: `{{ mustToDuration " " }}`, - }, { - name: "mustToDuration unsupported type", - tpl: `{{ mustToDuration . }}`, - vars: []int{1, 2, 3}, - }, { - name: "mustToDuration uint overflow", - tpl: `{{ mustToDuration . }}`, - vars: uint64(math.MaxInt64) + 1, - }, { - name: "mustToDuration int overflow", - tpl: `{{ mustToDuration . }}`, - vars: maxDurationSeconds + 1, - }, { - name: "mustToDuration int underflow", - tpl: `{{ mustToDuration . }}`, - vars: minDurationSeconds - 1, - }, { - name: "mustToDuration float overflow", - tpl: `{{ mustToDuration . }}`, - vars: maxDurationSecondsFloat + 0.5, - }, { - name: "mustToDuration float underflow", - tpl: `{{ mustToDuration . }}`, - vars: minDurationSecondsFloat - 0.5, - }, { - name: "mustToDuration NaN", - tpl: `{{ mustToDuration . }}`, - vars: math.NaN(), - }, { - name: "mustToDuration Inf", - tpl: `{{ mustToDuration . }}`, - vars: math.Inf(-1), - }, + }{ + { + name: "mustToDuration invalid string", + tpl: `{{ mustToDuration "nope" }}`, + }, { + name: "mustToDuration empty string", + tpl: `{{ mustToDuration "" }}`, + }, { + name: "mustToDuration whitespace string", + tpl: `{{ mustToDuration " " }}`, + }, { + name: "mustToDuration unsupported type", + tpl: `{{ mustToDuration . }}`, + vars: []int{1, 2, 3}, + }, { + name: "mustToDuration uint overflow", + tpl: `{{ mustToDuration . }}`, + vars: uint64(math.MaxInt64) + 1, + }, { + name: "mustToDuration int overflow", + tpl: `{{ mustToDuration . }}`, + vars: maxDurationSeconds + 1, + }, { + name: "mustToDuration int underflow", + tpl: `{{ mustToDuration . }}`, + vars: minDurationSeconds - 1, + }, { + name: "mustToDuration float overflow", + tpl: `{{ mustToDuration . }}`, + vars: maxDurationSecondsFloat + 0.5, + }, { + name: "mustToDuration float underflow", + tpl: `{{ mustToDuration . }}`, + vars: minDurationSecondsFloat - 0.5, + }, { + name: "mustToDuration NaN", + tpl: `{{ mustToDuration . }}`, + vars: math.NaN(), + }, { + name: "mustToDuration Inf", + tpl: `{{ mustToDuration . }}`, + vars: math.Inf(-1), + }, } for _, tt := range mustErrTests { diff --git a/pkg/getter/ocigetter.go b/pkg/getter/ocigetter.go index de8643bcd..b890d05d7 100644 --- a/pkg/getter/ocigetter.go +++ b/pkg/getter/ocigetter.go @@ -165,7 +165,6 @@ func (g *OCIGetter) newRegistryClient() (*registry.Client, error) { } client, err := registry.NewClient(opts...) - if err != nil { return nil, err } diff --git a/pkg/getter/plugingetter.go b/pkg/getter/plugingetter.go index c683a1090..0e048ccb3 100644 --- a/pkg/getter/plugingetter.go +++ b/pkg/getter/plugingetter.go @@ -19,7 +19,6 @@ import ( "bytes" "context" "fmt" - "net/url" "helm.sh/helm/v4/internal/plugin" diff --git a/pkg/getter/plugingetter_test.go b/pkg/getter/plugingetter_test.go index c9ed9f779..f36f48d00 100644 --- a/pkg/getter/plugingetter_test.go +++ b/pkg/getter/plugingetter_test.go @@ -17,7 +17,6 @@ package getter import ( "context" - "testing" "time" diff --git a/pkg/kube/client.go b/pkg/kube/client.go index bb26fbcc9..ba881c560 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -127,8 +127,10 @@ const ( FieldValidationDirectiveStrict FieldValidationDirective = "Strict" ) -type CreateApplyFunc func(target *resource.Info) error -type UpdateApplyFunc func(original, target *resource.Info) error +type ( + CreateApplyFunc func(target *resource.Info) error + UpdateApplyFunc func(original, target *resource.Info) error +) func init() { // Add CRDs to the scheme. They are missing by default. diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 405925a30..86574a3e5 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -2260,7 +2260,8 @@ metadata: } func createManifest(t *testing.T, manifest string, - fakeMapper meta.RESTMapper, fakeClient *dynamicfake.FakeDynamicClient) { + fakeMapper meta.RESTMapper, fakeClient *dynamicfake.FakeDynamicClient, +) { t.Helper() m := make(map[string]any) diff --git a/pkg/kube/converter.go b/pkg/kube/converter.go index 7300a708c..7bdc143ac 100644 --- a/pkg/kube/converter.go +++ b/pkg/kube/converter.go @@ -28,8 +28,10 @@ import ( "k8s.io/client-go/kubernetes/scheme" ) -var k8sNativeScheme *runtime.Scheme -var k8sNativeSchemeOnce sync.Once +var ( + k8sNativeScheme *runtime.Scheme + k8sNativeSchemeOnce sync.Once +) // AsVersioned converts the given info into a runtime.Object with the correct // group and version set @@ -41,7 +43,7 @@ func AsVersioned(info *resource.Info) runtime.Object { // RESTMapping. If no mapping is provided, the default schema versioner is used func convertWithMapper(obj runtime.Object, mapping *meta.RESTMapping) runtime.Object { s := kubernetesNativeScheme() - var gv = runtime.GroupVersioner(schema.GroupVersions(s.PrioritizedVersionsAllGroups())) + gv := runtime.GroupVersioner(schema.GroupVersions(s.PrioritizedVersionsAllGroups())) if mapping != nil { gv = mapping.GroupVersionKind.GroupVersion() } diff --git a/pkg/provenance/sign.go b/pkg/provenance/sign.go index 9b3e774cb..90b7559e5 100644 --- a/pkg/provenance/sign.go +++ b/pkg/provenance/sign.go @@ -218,7 +218,6 @@ func (s *Signatory) ClearSign(archiveData []byte, filename string, metadataBytes } _, err = io.Copy(w, b) - if err != nil { // NB: We intentionally don't call `w.Close()` here! `w.Close()` is the method which // actually does the PGP signing, and therefore is the part which uses the private key. diff --git a/pkg/registry/client.go b/pkg/registry/client.go index d1a744f4b..4c7b9a6c8 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -329,7 +329,6 @@ func ensureTLSConfig(client *auth.Client, setConfig *tls.Config) (*tls.Config, e func LoginOptInsecure(insecure bool) LoginOption { return func(o *loginOperation) { tlsConfig, err := ensureTLSConfig(o.client.authorizer, nil) - if err != nil { panic(err) } @@ -909,7 +908,8 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (string, *ur // tagManifest prepares and tags a manifest in memory storage func (c *Client) tagManifest(ctx context.Context, memoryStore *memory.Store, configDescriptor ocispec.Descriptor, layers []ocispec.Descriptor, - ociAnnotations map[string]string, parsedRef reference) (ocispec.Descriptor, error) { + ociAnnotations map[string]string, parsedRef reference, +) (ocispec.Descriptor, error) { manifest := ocispec.Manifest{ Versioned: specs.Versioned{SchemaVersion: 2}, Config: configDescriptor, diff --git a/pkg/registry/client_scope_test.go b/pkg/registry/client_scope_test.go index a790c27a7..733ce980d 100644 --- a/pkg/registry/client_scope_test.go +++ b/pkg/registry/client_scope_test.go @@ -40,6 +40,7 @@ func (suite *RegistryScopeTestSuite) SetupSuite() { // when the registry itself was reached over https. setup(&suite.TestRegistry, false, false, "token") } + func (suite *RegistryScopeTestSuite) TearDownSuite() { teardown(&suite.TestRegistry) os.RemoveAll(suite.WorkspaceDir) diff --git a/pkg/release/common.go b/pkg/release/common.go index 7d40effdc..eef48f099 100644 --- a/pkg/release/common.go +++ b/pkg/release/common.go @@ -77,7 +77,7 @@ func (a *v1Accessor) Version() int { } func (a *v1Accessor) Hooks() []Hook { - var hooks = make([]Hook, len(a.rel.Hooks)) + hooks := make([]Hook, len(a.rel.Hooks)) for i, h := range a.rel.Hooks { hooks[i] = h } @@ -141,7 +141,7 @@ func (a *v2Accessor) Version() int { } func (a *v2Accessor) Hooks() []Hook { - var hooks = make([]Hook, len(a.rel.Hooks)) + hooks := make([]Hook, len(a.rel.Hooks)) for i, h := range a.rel.Hooks { hooks[i] = h } diff --git a/pkg/release/v1/release.go b/pkg/release/v1/release.go index 3bbc0e4ce..bb99ec733 100644 --- a/pkg/release/v1/release.go +++ b/pkg/release/v1/release.go @@ -22,8 +22,10 @@ import ( type ApplyMethod string -const ApplyMethodClientSideApply ApplyMethod = "csa" -const ApplyMethodServerSideApply ApplyMethod = "ssa" +const ( + ApplyMethodClientSideApply ApplyMethod = "csa" + ApplyMethodServerSideApply ApplyMethod = "ssa" +) // Release describes a deployment of a chart, together with the chart // and the variables used to deploy that chart. diff --git a/pkg/release/v1/util/manifest_sorter_test.go b/pkg/release/v1/util/manifest_sorter_test.go index da08ab169..bdb36786f 100644 --- a/pkg/release/v1/util/manifest_sorter_test.go +++ b/pkg/release/v1/util/manifest_sorter_test.go @@ -61,7 +61,8 @@ metadata: annotations: "helm.sh/hook": post-install `, - }, { + }, + { name: []string{"third"}, path: "three", kind: []string{"ReplicaSet"}, @@ -73,7 +74,8 @@ metadata: annotations: "helm.sh/hook": no-such-hook `, - }, { + }, + { name: []string{"fourth"}, path: "four", kind: []string{"Pod"}, @@ -84,7 +86,8 @@ metadata: name: fourth annotations: nothing: here`, - }, { + }, + { name: []string{"fifth"}, path: "five", kind: []string{"ReplicaSet"}, @@ -96,14 +99,16 @@ metadata: annotations: "helm.sh/hook": post-delete, post-install `, - }, { + }, + { // Regression test: files with an underscore in the base name should be skipped. name: []string{"sixth"}, path: "six/_six", kind: []string{"ReplicaSet"}, hooks: map[string][]release.HookEvent{"sixth": nil}, manifest: `invalid manifest`, // This will fail if partial is not skipped. - }, { + }, + { // Regression test: files with no content should be skipped. name: []string{"seventh"}, path: "seven", diff --git a/pkg/repo/v1/index_test.go b/pkg/repo/v1/index_test.go index 09fc7de8a..9b46065a8 100644 --- a/pkg/repo/v1/index_test.go +++ b/pkg/repo/v1/index_test.go @@ -424,7 +424,6 @@ func TestIndexAdd(t *testing.T) { baseURL string digest string }{ - {&chart.Metadata{APIVersion: "v2", Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"}, {&chart.Metadata{APIVersion: "v2", Name: "alpine", Version: "0.1.0"}, "/home/charts/alpine-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"}, {&chart.Metadata{APIVersion: "v2", Name: "deis", Version: "0.1.0"}, "/home/charts/deis-0.1.0.tgz", "http://example.com/charts/", "sha256:1234567890"}, @@ -536,6 +535,7 @@ entries: home: https://github.com/something/else digest: "sha256:1234567890abcdef" ` + var indexWithDuplicatesInLastChartDeps = ` apiVersion: v1 entries: diff --git a/pkg/repo/v1/repo_test.go b/pkg/repo/v1/repo_test.go index f7f5df9be..2fe780ce3 100644 --- a/pkg/repo/v1/repo_test.go +++ b/pkg/repo/v1/repo_test.go @@ -137,15 +137,17 @@ func TestUpdateRepository(t *testing.T) { }, ) newRepoName := "sample" - sampleRepository.Update(&Entry{Name: newRepoName, - URL: "https://example.com/sample", + sampleRepository.Update(&Entry{ + Name: newRepoName, + URL: "https://example.com/sample", }) assert.Truef(t, sampleRepository.Has(newRepoName), "expected repository %s not found", newRepoName) repoCount := len(sampleRepository.Repositories) - sampleRepository.Update(&Entry{Name: newRepoName, - URL: "https://example.com/sample", + sampleRepository.Update(&Entry{ + Name: newRepoName, + URL: "https://example.com/sample", }) assert.Lenf(t, sampleRepository.Repositories, repoCount, "invalid number of repositories found %d, expected number of repositories %d", len(sampleRepository.Repositories), repoCount) diff --git a/pkg/storage/driver/labels_test.go b/pkg/storage/driver/labels_test.go index 34f01be79..81c2c5149 100644 --- a/pkg/storage/driver/labels_test.go +++ b/pkg/storage/driver/labels_test.go @@ -23,7 +23,7 @@ import ( ) func TestLabelsMatch(t *testing.T) { - var tests = []struct { + tests := []struct { desc string set1 labels set2 labels diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go index c0eff3cf9..745b12345 100644 --- a/pkg/storage/driver/memory_test.go +++ b/pkg/storage/driver/memory_test.go @@ -34,7 +34,7 @@ func TestMemoryName(t *testing.T) { } func TestMemoryCreate(t *testing.T) { - var tests = []struct { + tests := []struct { desc string rls *rspb.Release err bool @@ -76,7 +76,7 @@ func TestMemoryCreate(t *testing.T) { } func TestMemoryGet(t *testing.T) { - var tests = []struct { + tests := []struct { desc string key string namespace string @@ -133,7 +133,7 @@ func TestMemoryList(t *testing.T) { } func TestMemoryQuery(t *testing.T) { - var tests = []struct { + tests := []struct { desc string xlen int namespace string @@ -164,7 +164,7 @@ func TestMemoryQuery(t *testing.T) { } func TestMemoryUpdate(t *testing.T) { - var tests = []struct { + tests := []struct { desc string key string rls *rspb.Release @@ -215,7 +215,7 @@ func TestMemoryUpdate(t *testing.T) { } func TestMemoryDelete(t *testing.T) { - var tests = []struct { + tests := []struct { desc string key string namespace string diff --git a/pkg/storage/driver/records_test.go b/pkg/storage/driver/records_test.go index 22d54e6cc..049536971 100644 --- a/pkg/storage/driver/records_test.go +++ b/pkg/storage/driver/records_test.go @@ -31,7 +31,7 @@ func TestRecordsAdd(t *testing.T) { newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), }) - var tests = []struct { + tests := []struct { desc string key string ok bool @@ -62,7 +62,7 @@ func TestRecordsAdd(t *testing.T) { } func TestRecordsRemove(t *testing.T) { - var tests = []struct { + tests := []struct { desc string key string ok bool @@ -112,7 +112,7 @@ func TestRecordsGet(t *testing.T) { newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), }) - var tests = []struct { + tests := []struct { desc string key string rec *record @@ -141,7 +141,7 @@ func TestRecordsIndex(t *testing.T) { newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), }) - var tests = []struct { + tests := []struct { desc string key string sort int @@ -170,7 +170,7 @@ func TestRecordsExists(t *testing.T) { newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), }) - var tests = []struct { + tests := []struct { desc string key string ok bool @@ -199,7 +199,7 @@ func TestRecordsReplace(t *testing.T) { newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), }) - var tests = []struct { + tests := []struct { desc string key string rec *record diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go index 85e6cbd3f..db076da98 100644 --- a/pkg/storage/driver/sql.go +++ b/pkg/storage/driver/sql.go @@ -53,8 +53,10 @@ const postgreSQLDialect = "postgres" // SQLDriverName is the string name of this driver. const SQLDriverName = "SQL" -const sqlReleaseTableName = "releases_v1" -const sqlCustomLabelsTableName = "custom_labels_v1" +const ( + sqlReleaseTableName = "releases_v1" + sqlCustomLabelsTableName = "custom_labels_v1" +) const ( sqlReleaseTableKeyColumn = "key" @@ -359,7 +361,7 @@ func (s *SQL) List(filter func(release.Releaser) bool) ([]release.Releaser, erro return nil, err } - var records = []SQLReleaseWrapper{} + records := []SQLReleaseWrapper{} if err := s.db.Select(&records, query, args...); err != nil { s.Logger().Debug("failed to list", slog.Any("error", err)) return nil, err @@ -424,7 +426,7 @@ func (s *SQL) Query(labels map[string]string) ([]release.Releaser, error) { return nil, err } - var records = []SQLReleaseWrapper{} + records := []SQLReleaseWrapper{} if err := s.db.Select(&records, query, args...); err != nil { s.Logger().Debug("failed to query with labels", slog.Any("error", err)) return nil, err @@ -556,7 +558,6 @@ func (s *SQL) Create(key string, rel release.Releaser) error { k, v, ).ToSql() - if err != nil { defer transaction.Rollback() s.Logger().Debug("failed to build insert query", slog.Any("error", err)) @@ -603,7 +604,6 @@ func (s *SQL) Update(key string, rel release.Releaser) error { Where(sq.Eq{sqlReleaseTableKeyColumn: key}). Where(sq.Eq{sqlReleaseTableNamespaceColumn: namespace}). ToSql() - if err != nil { s.Logger().Debug("failed to build update query", slog.Any("error", err)) return err @@ -681,7 +681,6 @@ func (s *SQL) Delete(key string) (release.Releaser, error) { Where(sq.Eq{sqlCustomLabelsTableReleaseKeyColumn: key}). Where(sq.Eq{sqlCustomLabelsTableReleaseNamespaceColumn: s.namespace}). ToSql() - if err != nil { s.Logger().Debug("failed to build delete Labels query", slog.Any("error", err)) return nil, err @@ -695,14 +694,16 @@ func (s *SQL) getReleaseCustomLabels(key string, _ string) (map[string]string, e query, args, err := s.statementBuilder. Select(sqlCustomLabelsTableKeyColumn, sqlCustomLabelsTableValueColumn). From(sqlCustomLabelsTableName). - Where(sq.Eq{sqlCustomLabelsTableReleaseKeyColumn: key, - sqlCustomLabelsTableReleaseNamespaceColumn: s.namespace}). + Where(sq.Eq{ + sqlCustomLabelsTableReleaseKeyColumn: key, + sqlCustomLabelsTableReleaseNamespaceColumn: s.namespace, + }). ToSql() if err != nil { return nil, err } - var labelsList = []SQLReleaseCustomLabelWrapper{} + labelsList := []SQLReleaseCustomLabelWrapper{} if err := s.db.Select(&labelsList, query, args...); err != nil { return nil, err } diff --git a/pkg/storage/driver/util_test.go b/pkg/storage/driver/util_test.go index 5dda3cd1f..f84f6a67a 100644 --- a/pkg/storage/driver/util_test.go +++ b/pkg/storage/driver/util_test.go @@ -38,7 +38,7 @@ func TestIsSystemLabel(t *testing.T) { } func TestFilterSystemLabels(t *testing.T) { - var tests = [][2]map[string]string{ + tests := [][2]map[string]string{ {nil, map[string]string{}}, {map[string]string{}, map[string]string{}}, {map[string]string{ @@ -73,7 +73,7 @@ func TestFilterSystemLabels(t *testing.T) { } func TestContainsSystemLabels(t *testing.T) { - var tests = []struct { + tests := []struct { input map[string]string output bool }{ diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index 58cb56776..ae8513cca 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -137,7 +137,7 @@ func TestStorageList(t *testing.T) { require.NoError(t, storage.Create(rls6), "Storing release 'rls6'") } - var listTests = []struct { + listTests := []struct { Description string NumExpected int ListFunc func() ([]release.Releaser, error) @@ -263,24 +263,31 @@ type MaxHistoryMockDriver struct { func NewMaxHistoryMockDriver(d driver.Driver) *MaxHistoryMockDriver { return &MaxHistoryMockDriver{Driver: d} } + func (d *MaxHistoryMockDriver) Create(key string, rls release.Releaser) error { return d.Driver.Create(key, rls) } + func (d *MaxHistoryMockDriver) Update(key string, rls release.Releaser) error { return d.Driver.Update(key, rls) } + func (d *MaxHistoryMockDriver) Delete(_ string) (release.Releaser, error) { return nil, errMaxHistoryMockDriverSomethingHappened } + func (d *MaxHistoryMockDriver) Get(key string) (release.Releaser, error) { return d.Driver.Get(key) } + func (d *MaxHistoryMockDriver) List(filter func(release.Releaser) bool) ([]release.Releaser, error) { return d.Driver.List(filter) } + func (d *MaxHistoryMockDriver) Query(labels map[string]string) ([]release.Releaser, error) { return d.Driver.Query(labels) } + func (d *MaxHistoryMockDriver) Name() string { return d.Driver.Name() } diff --git a/pkg/strvals/literal_parser_test.go b/pkg/strvals/literal_parser_test.go index 94e0c6050..74ab775af 100644 --- a/pkg/strvals/literal_parser_test.go +++ b/pkg/strvals/literal_parser_test.go @@ -316,7 +316,8 @@ func TestParseLiteralInto(t *testing.T) { "outer": map[string]any{ "inner1": "value1,outer.inner3=value3,outer.inner4=4", "inner2": "value2", - }}, + }, + }, err: false, }, { diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go index c6c866711..73fc3f206 100644 --- a/pkg/strvals/parser_test.go +++ b/pkg/strvals/parser_test.go @@ -439,7 +439,8 @@ func TestParseInto(t *testing.T) { "inner2": "value2", "inner3": "value3", "inner4": 4, - }}, + }, + }, err: false, }, { From bc0114c70841e2a05c95e53e20358fb761dbfb2e Mon Sep 17 00:00:00 2001 From: Solomon Wakhungu <65043605+1solomonwakhungu@users.noreply.github.com> Date: Mon, 27 Jul 2026 13:55:05 -0500 Subject: [PATCH 515/571] fix(scripts): clarify cache-busting behavior Signed-off-by: Solomon Wakhungu <65043605+1solomonwakhungu@users.noreply.github.com> --- scripts/get-helm-3 | 3 ++- scripts/get-helm-4 | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/get-helm-3 b/scripts/get-helm-3 index 117ae3e03..a73b3da2a 100755 --- a/scripts/get-helm-3 +++ b/scripts/get-helm-3 @@ -114,12 +114,13 @@ verifySupported() { checkDesiredVersion() { if [ "x$DESIRED_VERSION" == "x" ]; then # Get tag from release URL + # Cache behavior is provider-specific, so this mitigation is best effort. # The current CDN does not revalidate on the request no-cache directive, # so use a unique query while retaining no-cache for compliant intermediaries. local latest_release_url="https://get.helm.sh/helm3-latest-version?ts=$(date +%s)" local latest_release_response="" if [ "${HAS_CURL}" == "true" ]; then - latest_release_response=$( curl -L --silent --show-error --fail -H "Cache-Control: no-cache" "$latest_release_url" 2>&1 || true ) + latest_release_response=$( curl -L --silent --show-error --fail --header "Cache-Control: no-cache" "$latest_release_url" 2>&1 || true ) elif [ "${HAS_WGET}" == "true" ]; then latest_release_response=$( wget "$latest_release_url" --header="Cache-Control: no-cache" -q -O - 2>&1 || true ) fi diff --git a/scripts/get-helm-4 b/scripts/get-helm-4 index 0ff312a7f..3d232cca9 100644 --- a/scripts/get-helm-4 +++ b/scripts/get-helm-4 @@ -114,12 +114,13 @@ verifySupported() { checkDesiredVersion() { if [ "x$DESIRED_VERSION" == "x" ]; then # Get tag from release URL + # Cache behavior is provider-specific, so this mitigation is best effort. # The current CDN does not revalidate on the request no-cache directive, # so use a unique query while retaining no-cache for compliant intermediaries. local latest_release_url="https://get.helm.sh/helm4-latest-version?ts=$(date +%s)" local latest_release_response="" if [ "${HAS_CURL}" == "true" ]; then - latest_release_response=$( curl -L --silent --show-error --fail -H "Cache-Control: no-cache" "$latest_release_url" 2>&1 || true ) + latest_release_response=$( curl -L --silent --show-error --fail --header "Cache-Control: no-cache" "$latest_release_url" 2>&1 || true ) elif [ "${HAS_WGET}" == "true" ]; then latest_release_response=$( wget "$latest_release_url" --header="Cache-Control: no-cache" -q -O - 2>&1 || true ) fi From ce27485634fcbd54be8fc8d99a5e4799456420c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 21:33:45 +0000 Subject: [PATCH 516/571] chore(deps): bump go.yaml.in/yaml/v3 from 3.0.4 to 3.0.5 Bumps [go.yaml.in/yaml/v3](https://github.com/yaml/go-yaml) from 3.0.4 to 3.0.5. - [Commits](https://github.com/yaml/go-yaml/compare/v3.0.4...v3.0.5) --- updated-dependencies: - dependency-name: go.yaml.in/yaml/v3 dependency-version: 3.0.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 6b9878bb2..13434162f 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 github.com/tetratelabs/wazero v1.12.0 - go.yaml.in/yaml/v3 v3.0.4 + go.yaml.in/yaml/v3 v3.0.5 golang.org/x/crypto v0.54.0 golang.org/x/term v0.45.0 golang.org/x/text v0.40.0 diff --git a/go.sum b/go.sum index e5933d99a..9afe18b88 100644 --- a/go.sum +++ b/go.sum @@ -376,8 +376,9 @@ go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= From 3c3be926aaea573cf94931877e8072aefb231b60 Mon Sep 17 00:00:00 2001 From: Ilya Kiselev Date: Tue, 28 Jul 2026 18:45:24 +0300 Subject: [PATCH 517/571] fix(chart): normalize StampModTimes timestamp + Chart.lock test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #32396. StampModTimes now normalizes the supplied time to UTC and truncates to whole seconds before stamping. Without this, an SDK caller passing a local-timezone or sub-second time.Time would produce a Chart.lock generated: field with a timezone offset or fractional seconds, making the lock file content non-reproducible across machines even when the same SOURCE_DATE_EPOCH value is used — defeating the purpose of the feature. Also add: - testdata/charts/chart-with-lock fixture: a minimal chart with a Chart.lock whose generated: timestamp predates SOURCE_DATE_EPOCH. - TestRunWithSourceDateEpochStampsLockGenerated: packages the fixture with SourceDateEpoch set to a non-UTC, sub-second time.Time and asserts that the resulting archive's Chart.lock entry has both the correct tar modtime and the correct (normalized) generated: value in the marshaled YAML. Signed-off-by: Ilya Kiselev --- pkg/action/package_test.go | 59 +++++++++++++++++++ .../charts/chart-with-lock/Chart.lock | 3 + .../charts/chart-with-lock/Chart.yaml | 4 ++ pkg/chart/v2/chart.go | 27 +++++---- 4 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 pkg/action/testdata/charts/chart-with-lock/Chart.lock create mode 100644 pkg/action/testdata/charts/chart-with-lock/Chart.yaml diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go index 421f34b33..d216d8341 100644 --- a/pkg/action/package_test.go +++ b/pkg/action/package_test.go @@ -17,9 +17,13 @@ limitations under the License. package action import ( + "archive/tar" + "compress/gzip" + "io" "os" "path" "testing" + "time" "github.com/Masterminds/semver/v3" "github.com/stretchr/testify/assert" @@ -144,3 +148,58 @@ func TestRun(t *testing.T) { require.Equal(t, "empty-0.1.0.tgz", filename) require.NoError(t, os.Remove(filename)) } + +// TestRunWithSourceDateEpochStampsLockGenerated verifies that packaging a chart +// that has a Chart.lock stamps both the tar entry modtime and the marshaled +// generated: field in Chart.lock to the given epoch. +// +// This guards against the normalization regression where a caller supplying a +// local-timezone or sub-second time.Time would produce a non-reproducible +// generated: value even when the same SOURCE_DATE_EPOCH is used on different +// machines. +func TestRunWithSourceDateEpochStampsLockGenerated(t *testing.T) { + // Use a non-local, non-UTC timezone and sub-second precision to confirm + // normalization: without UTC().Truncate(time.Second) the generated: field + // would contain a timezone offset or fractional seconds. + loc := time.FixedZone("UTC+3", 3*60*60) + rawEpoch := time.Unix(1700000000, 123456789).In(loc) + epoch := rawEpoch.UTC().Truncate(time.Second) + + client := NewPackage() + client.SourceDateEpoch = &rawEpoch + + filename, err := client.Run("testdata/charts/chart-with-lock", nil) + require.NoError(t, err) + t.Cleanup(func() { os.Remove(filename) }) + + f, err := os.Open(filename) + require.NoError(t, err) + defer f.Close() + + gr, err := gzip.NewReader(f) + require.NoError(t, err) + defer gr.Close() + + found := false + tr := tar.NewReader(gr) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + if path.Base(hdr.Name) != "Chart.lock" { + continue + } + found = true + require.True(t, epoch.Equal(hdr.ModTime), + "Chart.lock tar modtime: got %v, want %v", hdr.ModTime, epoch) + + raw, err := io.ReadAll(tr) + require.NoError(t, err) + wantGenerated := epoch.Format(time.RFC3339) + require.Contains(t, string(raw), wantGenerated, + "Chart.lock generated: field should contain normalized UTC timestamp") + } + require.True(t, found, "expected archive to contain a Chart.lock entry") +} diff --git a/pkg/action/testdata/charts/chart-with-lock/Chart.lock b/pkg/action/testdata/charts/chart-with-lock/Chart.lock new file mode 100644 index 000000000..824df0a40 --- /dev/null +++ b/pkg/action/testdata/charts/chart-with-lock/Chart.lock @@ -0,0 +1,3 @@ +dependencies: [] +digest: sha256:0000000000000000000000000000000000000000000000000000000000000 +generated: "2016-01-01T00:00:00Z" diff --git a/pkg/action/testdata/charts/chart-with-lock/Chart.yaml b/pkg/action/testdata/charts/chart-with-lock/Chart.yaml new file mode 100644 index 000000000..2cb1b3557 --- /dev/null +++ b/pkg/action/testdata/charts/chart-with-lock/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +name: chart-with-lock +version: 0.1.0 +description: Test chart with a Chart.lock, used to verify SourceDateEpoch stamping diff --git a/pkg/chart/v2/chart.go b/pkg/chart/v2/chart.go index 9772754ce..ccfe84f51 100644 --- a/pkg/chart/v2/chart.go +++ b/pkg/chart/v2/chart.go @@ -177,34 +177,41 @@ func (ch *Chart) CRDObjects() []CRD { return crds } -// StampModTimes sets timestamps on the chart (and dependencies) to epoch. -// This is used for reproducible builds via SOURCE_DATE_EPOCH. -func (ch *Chart) StampModTimes(epoch time.Time) { - ch.ModTime = epoch +// StampModTimes sets timestamps on the chart (and dependencies) to t, +// normalized to UTC and truncated to whole seconds. +// +// Normalization is required because Chart.lock's generated: field is written +// by yaml.Marshal from Lock.Generated. Without UTC/truncate, a caller +// supplying a local-zone or sub-second time.Time produces a generated: value +// with a timezone offset or fractional seconds, making the lock file content +// non-reproducible across machines even when the same SOURCE_DATE_EPOCH is used. +func (ch *Chart) StampModTimes(t time.Time) { + t = t.UTC().Truncate(time.Second) + ch.ModTime = t if len(ch.Schema) > 0 { - ch.SchemaModTime = epoch + ch.SchemaModTime = t } if ch.Lock != nil { - ch.Lock.Generated = epoch + ch.Lock.Generated = t } for _, f := range ch.Raw { if f != nil { - f.ModTime = epoch + f.ModTime = t } } for _, f := range ch.Templates { if f != nil { - f.ModTime = epoch + f.ModTime = t } } for _, f := range ch.Files { if f != nil { - f.ModTime = epoch + f.ModTime = t } } for _, dep := range ch.Dependencies() { - dep.StampModTimes(epoch) + dep.StampModTimes(t) } } From e92316751375bafd4621232bb39a1238a22e91c4 Mon Sep 17 00:00:00 2001 From: Ilya Kiselev Date: Tue, 28 Jul 2026 19:05:56 +0300 Subject: [PATCH 518/571] test(package): fix errorlint + exact path match in lock test - Replace `err == io.EOF` with `errors.Is(err, io.EOF)` (errorlint). - Match Chart.lock by full tar path ("chart-with-lock/Chart.lock") instead of path.Base to avoid false positives from subchart lock files. - Break out of the loop after finding the entry (early exit). Signed-off-by: Ilya Kiselev --- pkg/action/package_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go index d216d8341..2b359fa52 100644 --- a/pkg/action/package_test.go +++ b/pkg/action/package_test.go @@ -19,6 +19,7 @@ package action import ( "archive/tar" "compress/gzip" + "errors" "io" "os" "path" @@ -180,15 +181,16 @@ func TestRunWithSourceDateEpochStampsLockGenerated(t *testing.T) { require.NoError(t, err) defer gr.Close() + const wantPath = "chart-with-lock/Chart.lock" found := false tr := tar.NewReader(gr) for { hdr, err := tr.Next() - if err == io.EOF { + if errors.Is(err, io.EOF) { break } require.NoError(t, err) - if path.Base(hdr.Name) != "Chart.lock" { + if hdr.Name != wantPath { continue } found = true @@ -200,6 +202,7 @@ func TestRunWithSourceDateEpochStampsLockGenerated(t *testing.T) { wantGenerated := epoch.Format(time.RFC3339) require.Contains(t, string(raw), wantGenerated, "Chart.lock generated: field should contain normalized UTC timestamp") + break } - require.True(t, found, "expected archive to contain a Chart.lock entry") + require.True(t, found, "expected archive to contain %q entry", wantPath) } From ac1c68746f72e5bbbe5b7e5a052629aab7e5ab61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 15:39:05 -0600 Subject: [PATCH 519/571] chore(deps): bump actions/stale in the github-actions group (#32487) Bumps the github-actions group with 1 update: [actions/stale](https://github.com/actions/stale). Updates `actions/stale` from 10.4.0 to 11.0.0 - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/1e223db275d687790206a7acac4d1a11bd6fe629...4391f3da665fdf50b6810c1a66712fb9ba21aa93) --- updated-dependencies: - dependency-name: actions/stale dependency-version: 11.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 678952b03..e95a4b8c3 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -12,7 +12,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0 + - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.' From a8ab76e86f11e2ece5a3d0262313140e21db249b Mon Sep 17 00:00:00 2001 From: Nikolaus Schuetz Date: Tue, 28 Jul 2026 17:59:38 -0700 Subject: [PATCH 520/571] test(action): poll for the interrupted install goroutine instead of a zero-margin sleep (#32328) TestInstallRelease_Wait_Interrupted and TestInstallRelease_RollbackOnFailure_Interrupted assert that the detached installation goroutine has exited by sleeping a fixed 10s -- exactly the FailingKubeClient WaitDuration the goroutine blocks on. Sleeping the same duration the goroutine takes is a zero-margin race: if the goroutine finishes a hair after the fixed sleep elapses (scheduler jitter under load), getGoroutineCount() has not decremented yet and the assertion flakes. Replace the fixed sleep + point-in-time assert with is.Eventually polling getGoroutineCount() (30s ceiling, 10ms tick). It returns as soon as the goroutine has actually finished and no longer races the exact wait duration. Test-only; no change to install behavior. Signed-off-by: Nikolaus Schuetz --- pkg/action/install_test.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 80f3bd724..2d83abe27 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -653,8 +653,10 @@ func TestInstallRelease_Wait_Interrupted(t *testing.T) { req.ErrorContains(err, "context canceled") is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background - time.Sleep(10 * time.Second) // wait for goroutine to finish - is.Equal(goroutines, instAction.getGoroutineCount()) + // Poll until the background installation goroutine has finished. + is.Eventually(func() bool { + return instAction.getGoroutineCount() == goroutines + }, 30*time.Second, 10*time.Millisecond) } func TestInstallRelease_WaitForJobs(t *testing.T) { @@ -754,8 +756,10 @@ func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { req.Error(err) is.Equal(err, driver.ErrReleaseNotFound) is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background - time.Sleep(10 * time.Second) // wait for goroutine to finish - is.Equal(goroutines, instAction.getGoroutineCount()) + // Poll until the background installation goroutine has finished. + is.Eventually(func() bool { + return instAction.getGoroutineCount() == goroutines + }, 30*time.Second, 10*time.Millisecond) } func TestNameTemplate(t *testing.T) { From ae877c8bfb943d2cc1aa10b34f0c4fada55e6f56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 15:54:38 -0600 Subject: [PATCH 521/571] chore(deps): bump the github-actions group with 4 updates (#32508) Bumps the github-actions group with 4 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action). Updates `github/codeql-action/init` from 4.37.3 to 4.37.4 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81...f205ea1c3313d32999d8d6a48b4f6530d4437b38) Updates `github/codeql-action/autobuild` from 4.37.3 to 4.37.4 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81...f205ea1c3313d32999d8d6a48b4f6530d4437b38) Updates `github/codeql-action/analyze` from 4.37.3 to 4.37.4 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81...f205ea1c3313d32999d8d6a48b4f6530d4437b38) Updates `github/codeql-action/upload-sarif` from 4.37.3 to 4.37.4 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81...f205ea1c3313d32999d8d6a48b4f6530d4437b38) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.37.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/analyze dependency-version: 4.37.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.37.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d8c1a9d12..1c541c2bf 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # pinv4.37.3 + uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # pinv4.37.4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # pinv4.37.3 + uses: github/codeql-action/autobuild@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # pinv4.37.4 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # pinv4.37.3 + uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # pinv4.37.4 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 6c142b511..d631fa5ea 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3 + uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 with: sarif_file: results.sarif From 47eb219a71bac4638a9b9db916a5d8c415c553bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 22:06:50 +0000 Subject: [PATCH 522/571] chore(deps): bump the github-actions group with 4 updates Bumps the github-actions group with 4 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action). Updates `github/codeql-action/init` from 4.37.4 to 4.37.5 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/f205ea1c3313d32999d8d6a48b4f6530d4437b38...d1ba80a13dd99fba24a470575428917156a28b43) Updates `github/codeql-action/autobuild` from 4.37.4 to 4.37.5 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/f205ea1c3313d32999d8d6a48b4f6530d4437b38...d1ba80a13dd99fba24a470575428917156a28b43) Updates `github/codeql-action/analyze` from 4.37.4 to 4.37.5 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/f205ea1c3313d32999d8d6a48b4f6530d4437b38...d1ba80a13dd99fba24a470575428917156a28b43) Updates `github/codeql-action/upload-sarif` from 4.37.4 to 4.37.5 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/f205ea1c3313d32999d8d6a48b4f6530d4437b38...d1ba80a13dd99fba24a470575428917156a28b43) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.37.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/analyze dependency-version: 4.37.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.37.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1c541c2bf..775299964 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # pinv4.37.4 + uses: github/codeql-action/init@d1ba80a13dd99fba24a470575428917156a28b43 # pinv4.37.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # pinv4.37.4 + uses: github/codeql-action/autobuild@d1ba80a13dd99fba24a470575428917156a28b43 # pinv4.37.5 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # pinv4.37.4 + uses: github/codeql-action/analyze@d1ba80a13dd99fba24a470575428917156a28b43 # pinv4.37.5 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index d631fa5ea..926b0c3f6 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 + uses: github/codeql-action/upload-sarif@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: sarif_file: results.sarif From f8a308cd470bc1f087b3f4915ba775af18c5ffab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 05:31:22 -0600 Subject: [PATCH 523/571] chore(deps): bump github.com/santhosh-tekuri/jsonschema/v6 (#32514) Bumps [github.com/santhosh-tekuri/jsonschema/v6](https://github.com/santhosh-tekuri/jsonschema) from 6.0.2 to 6.0.3. - [Release notes](https://github.com/santhosh-tekuri/jsonschema/releases) - [Commits](https://github.com/santhosh-tekuri/jsonschema/compare/v6.0.2...v6.0.3) --- updated-dependencies: - dependency-name: github.com/santhosh-tekuri/jsonschema/v6 dependency-version: 6.0.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 13434162f..3843e17aa 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 github.com/rubenv/sql-migrate v1.8.1 - github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 + github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index 9afe18b88..46bc13845 100644 --- a/go.sum +++ b/go.sum @@ -282,8 +282,8 @@ github.com/rubenv/sql-migrate v1.8.1 h1:EPNwCvjAowHI3TnZ+4fQu3a915OpnQoPAjTXCGOy github.com/rubenv/sql-migrate v1.8.1/go.mod h1:BTIKBORjzyxZDS6dzoiw6eAFYJ1iNlGAtjn4LGeVjS8= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= From a0c2f6dade5180fdb08bcc5debef56357cd586ab Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Fri, 7 Aug 2026 08:12:24 -0600 Subject: [PATCH 524/571] fix: bump go.opentelemetry.io/otel to v1.44.0 for GO-2026-5158 (#32521) * fix: bump go.opentelemetry.io/otel to v1.44.0 for GO-2026-5158 govulncheck on main flags GO-2026-5158 in go.opentelemetry.io/otel@v1.43.0 (fixed in v1.44.0). Bump the transitive otel dependency to clear the vulnerability. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Terry Howe * fix: bump otel/sdk and otel/sdk/metric to v1.44.0 for version parity Keep the lockstep-versioned OpenTelemetry core and SDK modules aligned at v1.44.0 to avoid version skew. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Terry Howe --------- Signed-off-by: Terry Howe Co-authored-by: Claude Opus 4.8 (1M context) --- go.mod | 10 +++++----- go.sum | 22 ++++++++++++---------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 3843e17aa..6203377b7 100644 --- a/go.mod +++ b/go.mod @@ -138,7 +138,7 @@ require ( go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect - go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect @@ -151,11 +151,11 @@ require ( go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect go.opentelemetry.io/otel/log v0.19.0 // indirect - go.opentelemetry.io/otel/metric v1.43.0 // indirect - go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk v1.44.0 // indirect go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect - go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/mod v0.37.0 // indirect diff --git a/go.sum b/go.sum index 46bc13845..1b7a58218 100644 --- a/go.sum +++ b/go.sum @@ -328,8 +328,8 @@ go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9a go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= @@ -354,18 +354,20 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1J go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= +go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk= go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= From f3d68cdbea3076a0283cbd1e3ec44a409ad48a43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:53:05 -0600 Subject: [PATCH 525/571] chore(deps): bump the github-actions group with 4 updates (#32523) Bumps the github-actions group with 4 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action). Updates `github/codeql-action/init` from 4.37.5 to 4.37.6 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/d1ba80a13dd99fba24a470575428917156a28b43...5595ccaf912efad79be6eef63a5619ff05969be3) Updates `github/codeql-action/autobuild` from 4.37.5 to 4.37.6 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/d1ba80a13dd99fba24a470575428917156a28b43...5595ccaf912efad79be6eef63a5619ff05969be3) Updates `github/codeql-action/analyze` from 4.37.5 to 4.37.6 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/d1ba80a13dd99fba24a470575428917156a28b43...5595ccaf912efad79be6eef63a5619ff05969be3) Updates `github/codeql-action/upload-sarif` from 4.37.5 to 4.37.6 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/d1ba80a13dd99fba24a470575428917156a28b43...5595ccaf912efad79be6eef63a5619ff05969be3) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.37.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/analyze dependency-version: 4.37.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.37.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 775299964..5e392cc2c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@d1ba80a13dd99fba24a470575428917156a28b43 # pinv4.37.5 + uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # pinv4.37.6 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@d1ba80a13dd99fba24a470575428917156a28b43 # pinv4.37.5 + uses: github/codeql-action/autobuild@5595ccaf912efad79be6eef63a5619ff05969be3 # pinv4.37.6 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@d1ba80a13dd99fba24a470575428917156a28b43 # pinv4.37.5 + uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # pinv4.37.6 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 926b0c3f6..553a6b3da 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 + uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 with: sarif_file: results.sarif From 601445e88cf1fe66b2885c793d564030aad1d707 Mon Sep 17 00:00:00 2001 From: Dean Chen <862469039@qq.com> Date: Sat, 8 Aug 2026 11:46:06 +0500 Subject: [PATCH 526/571] fix(loader): do not drop values files ending at a 4096-byte boundary YAMLReader can return EOF without yielding the last line when that line has no trailing newline and its length is a multiple of bufio's default buffer. Read the file fully and ensure a trailing newline before parsing so compact JSON values files are not silently ignored. Fixes #32506 Signed-off-by: Dean Chen <862469039@qq.com> --- internal/chart/v3/loader/load.go | 14 +++++++++++++- internal/chart/v3/loader/load_test.go | 23 +++++++++++++++++++++++ pkg/chart/v2/loader/load.go | 14 +++++++++++++- pkg/chart/v2/loader/load_test.go | 23 +++++++++++++++++++++++ 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/internal/chart/v3/loader/load.go b/internal/chart/v3/loader/load.go index 48f346ccf..0ff040eae 100644 --- a/internal/chart/v3/loader/load.go +++ b/internal/chart/v3/loader/load.go @@ -183,8 +183,20 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { // The reader is expected to contain one or more YAML documents, the values of which are merged. // And the values can be either a chart's default values or user-supplied values. func LoadValues(data io.Reader) (map[string]any, error) { + // Read fully first. YAMLReader/LineReader can drop a final unterminated + // line when its length is an exact multiple of bufio.Reader's default + // buffer (4096). Appending a trailing newline avoids that case. + // See https://github.com/helm/helm/issues/32506 + b, err := io.ReadAll(data) + if err != nil { + return nil, err + } + if len(b) > 0 && b[len(b)-1] != '\n' { + b = append(b, '\n') + } + values := map[string]any{} - reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) + reader := utilyaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(b))) for { currentMap := map[string]any{} raw, err := reader.Read() diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index dd5fdc8db..f105b248f 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -419,6 +419,29 @@ foo: } } + +func TestLoadValuesEOFBoundary(t *testing.T) { + // Reproduces #32506: a single logical line whose length is a multiple of + // bufio's default buffer (4096) and has no trailing newline used to be + // dropped entirely by YAMLReader, yielding empty values. + prefix := []byte(`{"foo":"`) + suffix := []byte(`"}`) + pad := 4096 - len(prefix) - len(suffix) + data := make([]byte, 0, 4096) + data = append(data, prefix...) + data = append(data, bytes.Repeat([]byte("x"), pad)...) + data = append(data, suffix...) + if len(data) != 4096 { + t.Fatalf("test setup: want data length 4096, got %d", len(data)) + } + + values, err := LoadValues(bytes.NewReader(data)) + require.NoError(t, err) + assert.Equal(t, map[string]any{ + "foo": string(bytes.Repeat([]byte("x"), pad)), + }, values) +} + func TestMergeValuesV3(t *testing.T) { nestedMap := map[string]any{ "foo": "bar", diff --git a/pkg/chart/v2/loader/load.go b/pkg/chart/v2/loader/load.go index 28115d062..d7b125b9b 100644 --- a/pkg/chart/v2/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -210,8 +210,20 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { // The reader is expected to contain one or more YAML documents, the values of which are merged. // And the values can be either a chart's default values or user-supplied values. func LoadValues(data io.Reader) (map[string]any, error) { + // Read fully first. YAMLReader/LineReader can drop a final unterminated + // line when its length is an exact multiple of bufio.Reader's default + // buffer (4096). Appending a trailing newline avoids that case. + // See https://github.com/helm/helm/issues/32506 + b, err := io.ReadAll(data) + if err != nil { + return nil, err + } + if len(b) > 0 && b[len(b)-1] != '\n' { + b = append(b, '\n') + } + values := map[string]any{} - reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) + reader := utilyaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(b))) for { currentMap := map[string]any{} raw, err := reader.Read() diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index 8bf254321..b4836ee95 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -463,6 +463,29 @@ foo: } } + +func TestLoadValuesEOFBoundary(t *testing.T) { + // Reproduces #32506: a single logical line whose length is a multiple of + // bufio's default buffer (4096) and has no trailing newline used to be + // dropped entirely by YAMLReader, yielding empty values. + prefix := []byte(`{"foo":"`) + suffix := []byte(`"}`) + pad := 4096 - len(prefix) - len(suffix) + data := make([]byte, 0, 4096) + data = append(data, prefix...) + data = append(data, bytes.Repeat([]byte("x"), pad)...) + data = append(data, suffix...) + if len(data) != 4096 { + t.Fatalf("test setup: want data length 4096, got %d", len(data)) + } + + values, err := LoadValues(bytes.NewReader(data)) + require.NoError(t, err) + assert.Equal(t, map[string]any{ + "foo": string(bytes.Repeat([]byte("x"), pad)), + }, values) +} + func TestMergeValuesV2(t *testing.T) { nestedMap := map[string]any{ "foo": "bar", From fd4ed4952b5f9f54344a0c014f7acd7885bd60c6 Mon Sep 17 00:00:00 2001 From: Dean Chen <862469039@qq.com> Date: Sat, 8 Aug 2026 19:45:24 +0500 Subject: [PATCH 527/571] test(loader): cover 8192-byte EOF boundary for LoadValues Also drop the extra blank line before the test. Addresses review on #32525. Signed-off-by: Dean Chen <862469039@qq.com> --- internal/chart/v3/loader/load_test.go | 37 +++++++++++++++------------ pkg/chart/v2/loader/load_test.go | 37 +++++++++++++++------------ 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index f105b248f..e34364f39 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -21,6 +21,7 @@ import ( "bytes" "compress/gzip" "errors" + "fmt" "io" "log" "os" @@ -419,27 +420,31 @@ foo: } } - func TestLoadValuesEOFBoundary(t *testing.T) { // Reproduces #32506: a single logical line whose length is a multiple of // bufio's default buffer (4096) and has no trailing newline used to be // dropped entirely by YAMLReader, yielding empty values. - prefix := []byte(`{"foo":"`) - suffix := []byte(`"}`) - pad := 4096 - len(prefix) - len(suffix) - data := make([]byte, 0, 4096) - data = append(data, prefix...) - data = append(data, bytes.Repeat([]byte("x"), pad)...) - data = append(data, suffix...) - if len(data) != 4096 { - t.Fatalf("test setup: want data length 4096, got %d", len(data)) - } + // Also cover 8192 (2x buffer) so we do not only hit the single-buffer case. + for _, size := range []int{4096, 8192} { + t.Run(fmt.Sprintf("size_%d", size), func(t *testing.T) { + prefix := []byte(`{"foo":"`) + suffix := []byte(`"}`) + pad := size - len(prefix) - len(suffix) + data := make([]byte, 0, size) + data = append(data, prefix...) + data = append(data, bytes.Repeat([]byte("x"), pad)...) + data = append(data, suffix...) + if len(data) != size { + t.Fatalf("test setup: want data length %d, got %d", size, len(data)) + } - values, err := LoadValues(bytes.NewReader(data)) - require.NoError(t, err) - assert.Equal(t, map[string]any{ - "foo": string(bytes.Repeat([]byte("x"), pad)), - }, values) + values, err := LoadValues(bytes.NewReader(data)) + require.NoError(t, err) + assert.Equal(t, map[string]any{ + "foo": string(bytes.Repeat([]byte("x"), pad)), + }, values) + }) + } } func TestMergeValuesV3(t *testing.T) { diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index b4836ee95..ae1da1130 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -21,6 +21,7 @@ import ( "bytes" "compress/gzip" "errors" + "fmt" "io" "log" "os" @@ -463,27 +464,31 @@ foo: } } - func TestLoadValuesEOFBoundary(t *testing.T) { // Reproduces #32506: a single logical line whose length is a multiple of // bufio's default buffer (4096) and has no trailing newline used to be // dropped entirely by YAMLReader, yielding empty values. - prefix := []byte(`{"foo":"`) - suffix := []byte(`"}`) - pad := 4096 - len(prefix) - len(suffix) - data := make([]byte, 0, 4096) - data = append(data, prefix...) - data = append(data, bytes.Repeat([]byte("x"), pad)...) - data = append(data, suffix...) - if len(data) != 4096 { - t.Fatalf("test setup: want data length 4096, got %d", len(data)) - } + // Also cover 8192 (2x buffer) so we do not only hit the single-buffer case. + for _, size := range []int{4096, 8192} { + t.Run(fmt.Sprintf("size_%d", size), func(t *testing.T) { + prefix := []byte(`{"foo":"`) + suffix := []byte(`"}`) + pad := size - len(prefix) - len(suffix) + data := make([]byte, 0, size) + data = append(data, prefix...) + data = append(data, bytes.Repeat([]byte("x"), pad)...) + data = append(data, suffix...) + if len(data) != size { + t.Fatalf("test setup: want data length %d, got %d", size, len(data)) + } - values, err := LoadValues(bytes.NewReader(data)) - require.NoError(t, err) - assert.Equal(t, map[string]any{ - "foo": string(bytes.Repeat([]byte("x"), pad)), - }, values) + values, err := LoadValues(bytes.NewReader(data)) + require.NoError(t, err) + assert.Equal(t, map[string]any{ + "foo": string(bytes.Repeat([]byte("x"), pad)), + }, values) + }) + } } func TestMergeValuesV2(t *testing.T) { From edb94f897d1e0ec3335fb7268f07e0fd2666fbb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 15:43:12 -0600 Subject: [PATCH 528/571] chore(deps): bump golang.org/x/text from 0.40.0 to 0.41.0 (#32543) Bumps [golang.org/x/text](https://github.com/golang/text) from 0.40.0 to 0.41.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.40.0...v0.41.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.41.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 6203377b7..a2f06e39f 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( go.yaml.in/yaml/v3 v3.0.5 golang.org/x/crypto v0.54.0 golang.org/x/term v0.45.0 - golang.org/x/text v0.40.0 + golang.org/x/text v0.41.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.36.3 k8s.io/apiextensions-apiserver v0.36.3 @@ -158,13 +158,13 @@ require ( go.opentelemetry.io/otel/trace v1.44.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/mod v0.37.0 // indirect - golang.org/x/net v0.56.0 // indirect + golang.org/x/mod v0.38.0 // indirect + golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.47.0 // indirect + golang.org/x/tools v0.48.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect google.golang.org/grpc v1.82.1 // indirect diff --git a/go.sum b/go.sum index 1b7a58218..75ac7bb0e 100644 --- a/go.sum +++ b/go.sum @@ -393,8 +393,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= -golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -405,8 +405,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -453,8 +453,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -463,8 +463,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= -golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= From 2a29f1770b62844b27197d2507377361d45ad7c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 15:50:55 -0600 Subject: [PATCH 529/571] chore(deps): bump golang.org/x/crypto from 0.54.0 to 0.55.0 (#32545) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.54.0 to 0.55.0. - [Commits](https://github.com/golang/crypto/compare/v0.54.0...v0.55.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.55.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a2f06e39f..4c0468708 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/tetratelabs/wazero v1.12.0 go.yaml.in/yaml/v3 v3.0.5 - golang.org/x/crypto v0.54.0 + golang.org/x/crypto v0.55.0 golang.org/x/term v0.45.0 golang.org/x/text v0.41.0 gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 75ac7bb0e..35d9e4465 100644 --- a/go.sum +++ b/go.sum @@ -387,8 +387,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= -golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= +golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= From bafdcde8ecd83119afd7f98363b364138d3b35dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:44:55 -0600 Subject: [PATCH 530/571] chore(deps): bump github.com/stretchr/testify from 1.11.1 to 1.12.0 (#32555) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.11.1 to 1.12.0. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.11.1...v1.12.0) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-version: 1.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 4c0468708..6a691efac 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 - github.com/stretchr/testify v1.11.1 + github.com/stretchr/testify v1.12.0 github.com/tetratelabs/wazero v1.12.0 go.yaml.in/yaml/v3 v3.0.5 golang.org/x/crypto v0.55.0 diff --git a/go.sum b/go.sum index 35d9e4465..54d14492a 100644 --- a/go.sum +++ b/go.sum @@ -301,14 +301,14 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI= +github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw= github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q= github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk= github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU= From c000a408b81266d328db64aea2ff9e2e70844790 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:58:11 -0600 Subject: [PATCH 531/571] chore(deps): bump the github-actions group across 1 directory with 4 updates (#32556) Bumps the github-actions group with 4 updates in the / directory: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action). Updates `github/codeql-action/init` from 4.37.6 to 4.37.7 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd) Updates `github/codeql-action/autobuild` from 4.37.6 to 4.37.7 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd) Updates `github/codeql-action/analyze` from 4.37.6 to 4.37.7 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd) Updates `github/codeql-action/upload-sarif` from 4.37.6 to 4.37.7 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd) --- updated-dependencies: - dependency-name: github/codeql-action/analyze dependency-version: 4.37.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/init dependency-version: 4.37.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.37.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 5e392cc2c..6bfec7108 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # pinv4.37.6 + uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # pinv4.37.7 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@5595ccaf912efad79be6eef63a5619ff05969be3 # pinv4.37.6 + uses: github/codeql-action/autobuild@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # pinv4.37.7 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # pinv4.37.6 + uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # pinv4.37.7 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 553a6b3da..0a12abf49 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: results.sarif From e9b85e4de16ad45805843fb121748018cf89094b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 15:58:20 -0600 Subject: [PATCH 532/571] chore(deps): bump github.com/stretchr/testify from 1.12.0 to 1.12.1 (#32562) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.12.0 to 1.12.1. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.12.0...v1.12.1) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-version: 1.12.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6a691efac..c34a80649 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 - github.com/stretchr/testify v1.12.0 + github.com/stretchr/testify v1.12.1 github.com/tetratelabs/wazero v1.12.0 go.yaml.in/yaml/v3 v3.0.5 golang.org/x/crypto v0.55.0 diff --git a/go.sum b/go.sum index 54d14492a..bfac195f1 100644 --- a/go.sum +++ b/go.sum @@ -307,8 +307,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI= -github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q= github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk= github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU= From 67d54fd8808ab47e93dad40f4238d5fe28d1efd0 Mon Sep 17 00:00:00 2001 From: Ruslan Shaydullin Date: Sun, 23 Aug 2026 17:24:34 +0500 Subject: [PATCH 533/571] fix(provenance): support GnuPG keybox (pubring.kbx) keyrings (#32281) * fix(provenance): support GnuPG keybox (pubring.kbx) keyrings Starting with GnuPG 2.1, file-backed public keyrings can use ~/.gnupg/pubring.kbx instead of the legacy pubring.gpg. Helm only read the legacy format, so chart and plugin verification failed on installations using the file-backed keybox. Make the keyring loader format-aware: - GnuPG keybox (pubring.kbx): extract OpenPGP keyblocks from the keybox container without adding a dependency. Skip ephemeral blobs, matching GnuPG's own read behavior. - ASCII-armored keyrings: load single or concatenated exports. - Legacy binary packet streams (pubring.gpg): retain the existing path. defaultKeyring() falls back to pubring.kbx when pubring.gpg is absent. pubring.gpg keeps precedence when both files exist. This change covers file-backed public keyrings. It does not read the SQLite database used by keyboxd, which needs a separate design. Related to #31836 Signed-off-by: Ruslan Shaydullin * fix(provenance): treat only not-exist as keyring absence in defaultKeyring A stat error other than 'not exist' (e.g. a permission problem) meant the file may well be present, but defaultKeyring() skipped past it: an unreadable pubring.gpg silently lost precedence to pubring.kbx, and the surfaced error could point at the wrong file. Treat only fs.ErrNotExist as absence. For any other stat error, return that path unchanged so the real error surfaces when the keyring is opened. Signed-off-by: Ruslan Shaydullin --------- Signed-off-by: Ruslan Shaydullin Co-authored-by: George Jenkins --- pkg/cmd/dependency_build.go | 21 +- pkg/cmd/dependency_build_test.go | 66 ++++++ pkg/provenance/keybox.go | 113 +++++++++++ pkg/provenance/keybox_test.go | 188 ++++++++++++++++++ pkg/provenance/sign.go | 58 +++++- pkg/provenance/sign_test.go | 93 +++++++++ .../testdata/helm-mixed-keyring.asc | 40 ++++ .../testdata/helm-mixed-keyring.kbx | Bin 0 -> 1865 bytes pkg/provenance/testdata/helm-test-key.asc | 30 +++ pkg/provenance/testdata/helm-test-key.kbx | Bin 0 -> 1465 bytes .../testdata/regen-keyring-formats.sh | 26 +++ 11 files changed, 630 insertions(+), 5 deletions(-) create mode 100644 pkg/provenance/keybox.go create mode 100644 pkg/provenance/keybox_test.go create mode 100644 pkg/provenance/testdata/helm-mixed-keyring.asc create mode 100644 pkg/provenance/testdata/helm-mixed-keyring.kbx create mode 100644 pkg/provenance/testdata/helm-test-key.asc create mode 100644 pkg/provenance/testdata/helm-test-key.kbx create mode 100755 pkg/provenance/testdata/regen-keyring-formats.sh diff --git a/pkg/cmd/dependency_build.go b/pkg/cmd/dependency_build.go index 85c385670..2fb997e4c 100644 --- a/pkg/cmd/dependency_build.go +++ b/pkg/cmd/dependency_build.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "io" + "io/fs" "os" "path/filepath" @@ -97,9 +98,25 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command { } // defaultKeyring returns the expanded path to the default keyring. +// +// The legacy pubring.gpg file is preferred and treated as absent only when +// stat fails with "not exist" — any other stat error (e.g. permissions) +// keeps the legacy path so the real error surfaces when the file is opened. +// If the legacy file is absent, the file-backed pubring.kbx path takes over +// under the same rule. When neither exists, the legacy path is returned so +// error messages keep pointing at the traditional default. func defaultKeyring() string { + gnupgHome := filepath.Join(homedir.HomeDir(), ".gnupg") if v, ok := os.LookupEnv("GNUPGHOME"); ok { - return filepath.Join(v, "pubring.gpg") + gnupgHome = v } - return filepath.Join(homedir.HomeDir(), ".gnupg", "pubring.gpg") + legacy := filepath.Join(gnupgHome, "pubring.gpg") + if _, err := os.Stat(legacy); !errors.Is(err, fs.ErrNotExist) { + return legacy + } + keybox := filepath.Join(gnupgHome, "pubring.kbx") + if _, err := os.Stat(keybox); !errors.Is(err, fs.ErrNotExist) { + return keybox + } + return legacy } diff --git a/pkg/cmd/dependency_build_test.go b/pkg/cmd/dependency_build_test.go index 1680c23a1..05e27076f 100644 --- a/pkg/cmd/dependency_build_test.go +++ b/pkg/cmd/dependency_build_test.go @@ -19,6 +19,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "testing" "github.com/stretchr/testify/assert" @@ -140,3 +141,68 @@ func TestDependencyBuildCmdWithHelmV2Hash(t *testing.T) { t.Fatal(err) } } + +func TestDefaultKeyring(t *testing.T) { + touch := func(t *testing.T, path string) { + t.Helper() + require.NoError(t, os.WriteFile(path, []byte("test"), 0o644)) + } + + tests := []struct { + name string + files []string + want string + }{ + {"legacy keyring only", []string{"pubring.gpg"}, "pubring.gpg"}, + {"keybox only", []string{"pubring.kbx"}, "pubring.kbx"}, + {"legacy keyring preferred over keybox", []string{"pubring.gpg", "pubring.kbx"}, "pubring.gpg"}, + {"neither present falls back to legacy path", nil, "pubring.gpg"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + t.Setenv("GNUPGHOME", dir) + for _, f := range tt.files { + touch(t, filepath.Join(dir, f)) + } + assert.Equal(t, filepath.Join(dir, tt.want), defaultKeyring()) + }) + } + + t.Run("stat error other than not-exist keeps the legacy path", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("directory permissions are not enforced on Windows") + } + if os.Geteuid() == 0 { + t.Skip("root bypasses directory permissions") + } + + parent := t.TempDir() + dir := filepath.Join(parent, ".gnupg") + require.NoError(t, os.MkdirAll(dir, 0o700)) + touch(t, filepath.Join(dir, "pubring.kbx")) + t.Setenv("GNUPGHOME", dir) + + // Make the directory unsearchable so stat on both keyrings fails + // with a permission error rather than "not exist". + require.NoError(t, os.Chmod(dir, 0o000)) + t.Cleanup(func() { require.NoError(t, os.Chmod(dir, 0o700)) }) + + assert.Equal(t, filepath.Join(dir, "pubring.gpg"), defaultKeyring()) + }) + + t.Run("no GNUPGHOME falls back to the home directory", func(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + t.Setenv("GNUPGHOME", home) // register restoration before unsetting + require.NoError(t, os.Unsetenv("GNUPGHOME")) + + gnupgDir := filepath.Join(home, ".gnupg") + require.NoError(t, os.MkdirAll(gnupgDir, 0o700)) + + assert.Equal(t, filepath.Join(gnupgDir, "pubring.gpg"), defaultKeyring()) + + touch(t, filepath.Join(gnupgDir, "pubring.kbx")) + assert.Equal(t, filepath.Join(gnupgDir, "pubring.kbx"), defaultKeyring()) + }) +} diff --git a/pkg/provenance/keybox.go b/pkg/provenance/keybox.go new file mode 100644 index 000000000..99f220b99 --- /dev/null +++ b/pkg/provenance/keybox.go @@ -0,0 +1,113 @@ +/* +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 provenance + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" +) + +// GnuPG 2.1+ can store file-backed public keys in a "keybox" (pubring.kbx), +// a container that interleaves OpenPGP keyblocks with GnuPG-specific +// metadata. It is not an OpenPGP packet stream, so it cannot be handed to +// openpgp.ReadKeyRing directly. A keybox is a sequence of blobs, each starting +// with: +// +// byte 0..3 u32 blob length, big endian, including this header +// byte 4 u8 blob type (0 empty, 1 header, 2 OpenPGP, 3 X.509) +// byte 5 u8 blob version +// +// The first blob is a header carrying the "KBXf" magic at offset 8. OpenPGP +// blobs (type 2) record where the raw keyblock lives inside the blob: +// +// byte 8..11 u32 keyblock offset, relative to the blob start +// byte 12..15 u32 keyblock length +// +// Reference: kbx/keybox-blob.c in the GnuPG source tree. +const ( + kbxBlobTypeHeader = 1 + kbxBlobTypeOpenPGP = 2 + + // kbxBlobFlagEphemeral marks a blob GnuPG considers not (yet) part of + // the keyring, e.g. written during an interrupted keyserver operation + // (KEYBOX_FLAG_BLOB_EPHEMERAL in kbx/keybox.h). GnuPG skips such blobs + // on every normal read (kbx/keybox-search.c), and so do we. + kbxBlobFlagEphemeral = 0x0002 + + // kbxMinBlobLen covers the length and type fields present in every blob. + kbxMinBlobLen = 5 + + // kbxOpenPGPHeaderLen is how much of an OpenPGP blob header must be + // present for the flags, keyblock offset and keyblock length fields to + // be readable. + kbxOpenPGPHeaderLen = 16 +) + +// isKeybox reports whether data looks like a GnuPG keybox (pubring.kbx) +// image, identified by the "KBXf" magic in the mandatory first header blob. +func isKeybox(data []byte) bool { + return len(data) >= 12 && data[4] == kbxBlobTypeHeader && string(data[8:12]) == "KBXf" +} + +// isArmored reports whether data looks like an ASCII-armored keyring, as +// produced by `gpg --export --armor`. +func isArmored(data []byte) bool { + return bytes.HasPrefix(bytes.TrimSpace(data), []byte("-----BEGIN PGP")) +} + +// keyboxPublicKeys extracts the OpenPGP keyblocks embedded in a keybox image +// and returns them concatenated, ready for openpgp.ReadKeyRing. Blobs of any +// other type (header, X.509, empty) are skipped, as are blobs flagged +// ephemeral, which GnuPG itself ignores when reading the keyring. Malformed +// input yields an error, never a panic. +func keyboxPublicKeys(data []byte) ([]byte, error) { + var keyblocks bytes.Buffer + for offset := 0; offset < len(data); { + rest := data[offset:] + if len(rest) < kbxMinBlobLen { + return nil, fmt.Errorf("truncated blob header at offset %d", offset) + } + blobLen := binary.BigEndian.Uint32(rest) + if blobLen < kbxMinBlobLen { + return nil, fmt.Errorf("invalid blob length %d at offset %d", blobLen, offset) + } + if uint64(blobLen) > uint64(len(rest)) { + return nil, fmt.Errorf("blob at offset %d has length %d exceeding the %d remaining bytes", offset, blobLen, len(rest)) + } + blob := rest[:blobLen] + if blob[4] == kbxBlobTypeOpenPGP { + if len(blob) < kbxOpenPGPHeaderLen { + return nil, fmt.Errorf("OpenPGP blob at offset %d is too short", offset) + } + flags := binary.BigEndian.Uint16(blob[6:]) + keyblockOffset := binary.BigEndian.Uint32(blob[8:]) + keyblockLen := binary.BigEndian.Uint32(blob[12:]) + if uint64(keyblockOffset)+uint64(keyblockLen) > uint64(len(blob)) { + return nil, fmt.Errorf("OpenPGP blob at offset %d has an out-of-range keyblock", offset) + } + if flags&kbxBlobFlagEphemeral == 0 { + keyblocks.Write(blob[keyblockOffset : keyblockOffset+keyblockLen]) + } + } + offset += int(blobLen) + } + if keyblocks.Len() == 0 { + return nil, errors.New("keybox contains no OpenPGP keys") + } + return keyblocks.Bytes(), nil +} diff --git a/pkg/provenance/keybox_test.go b/pkg/provenance/keybox_test.go new file mode 100644 index 000000000..e2133d45a --- /dev/null +++ b/pkg/provenance/keybox_test.go @@ -0,0 +1,188 @@ +/* +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 provenance + +import ( + "bytes" + "encoding/binary" + "os" + "testing" + + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + // testKeybox is a GnuPG keybox (pubring.kbx) containing the helm-test + // public key. Regenerate with testdata/regen-keyring-formats.sh. + testKeybox = "testdata/helm-test-key.kbx" + + // testMixedKeybox is a keybox containing the RSA and Ed25519 test keys. + testMixedKeybox = "testdata/helm-mixed-keyring.kbx" + + // testArmoredPubfile is the ASCII-armored export of the helm-test key. + testArmoredPubfile = "testdata/helm-test-key.asc" + + // testMultiBlockArmored is two concatenated single-key armored exports + // (cat key1.asc key2.asc), covering the RSA and Ed25519 test keys. + testMultiBlockArmored = "testdata/helm-mixed-keyring.asc" +) + +func TestIsKeybox(t *testing.T) { + tests := []struct { + name string + file string + want bool + }{ + {"keybox", testKeybox, true}, + {"mixed keybox", testMixedKeybox, true}, + {"legacy binary keyring", testPubfile, false}, + {"armored keyring", testArmoredPubfile, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := os.ReadFile(tt.file) + require.NoError(t, err) + assert.Equal(t, tt.want, isKeybox(data)) + }) + } + + t.Run("degenerate inputs", func(t *testing.T) { + assert.False(t, isKeybox(nil)) + assert.False(t, isKeybox([]byte{})) + assert.False(t, isKeybox([]byte("KBXf"))) + assert.False(t, isKeybox([]byte("garbage that is longer than twelve bytes"))) + }) +} + +func TestIsArmored(t *testing.T) { + tests := []struct { + name string + file string + want bool + }{ + {"armored keyring", testArmoredPubfile, true}, + {"legacy binary keyring", testPubfile, false}, + {"keybox", testKeybox, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := os.ReadFile(tt.file) + require.NoError(t, err) + assert.Equal(t, tt.want, isArmored(data)) + }) + } + + t.Run("degenerate inputs", func(t *testing.T) { + assert.False(t, isArmored(nil)) + assert.False(t, isArmored([]byte("not a key"))) + assert.True(t, isArmored([]byte("\n\t -----BEGIN PGP PUBLIC KEY BLOCK-----"))) + }) +} + +func TestKeyboxPublicKeys(t *testing.T) { + data, err := os.ReadFile(testKeybox) + require.NoError(t, err) + + keys, err := keyboxPublicKeys(data) + require.NoError(t, err) + + ring, err := openpgp.ReadKeyRing(bytes.NewReader(keys)) + require.NoError(t, err) + + require.Len(t, ring, 1) + _, ok := ring[0].Identities[testKeyName] + assert.True(t, ok, "expected keybox to contain %q", testKeyName) +} + +func TestKeyboxPublicKeysEphemeral(t *testing.T) { + // GnuPG flags in-progress key material as ephemeral (bit 0x0002 of the + // blob flags at blob offset 6) and hides it from every normal read; the + // parser must do the same. + setFlags := func(t *testing.T, data []byte, blobStart int, flags uint16) []byte { + t.Helper() + require.Equal(t, byte(kbxBlobTypeOpenPGP), data[blobStart+4]) + mutated := bytes.Clone(data) + binary.BigEndian.PutUint16(mutated[blobStart+6:], flags) + return mutated + } + + t.Run("all blobs ephemeral means no keys", func(t *testing.T) { + valid, err := os.ReadFile(testKeybox) + require.NoError(t, err) + + _, err = keyboxPublicKeys(setFlags(t, valid, 32, kbxBlobFlagEphemeral)) + assert.ErrorContains(t, err, "no OpenPGP keys") + }) + + t.Run("ephemeral blob is skipped, others kept", func(t *testing.T) { + valid, err := os.ReadFile(testMixedKeybox) + require.NoError(t, err) + + // Flag only the first OpenPGP blob (the RSA helm-test key). + keys, err := keyboxPublicKeys(setFlags(t, valid, 32, kbxBlobFlagEphemeral)) + require.NoError(t, err) + + ring, err := openpgp.ReadKeyRing(bytes.NewReader(keys)) + require.NoError(t, err) + + require.Len(t, ring, 1) + _, ok := ring[0].Identities[testKeyName] + assert.False(t, ok, "expected the ephemeral-flagged %q blob to be skipped", testKeyName) + }) +} + +func TestKeyboxPublicKeysMalformed(t *testing.T) { + valid, err := os.ReadFile(testKeybox) + require.NoError(t, err) + + // The mutations below rely on the fixture layout: a 32-byte header blob + // followed by an OpenPGP blob. + const blobStart = 32 + require.Greater(t, len(valid), blobStart+16) + require.Equal(t, byte(kbxBlobTypeOpenPGP), valid[blobStart+4]) + + mutate := func(offset int, value uint32) []byte { + data := bytes.Clone(valid) + binary.BigEndian.PutUint32(data[offset:], value) + return data + } + + tests := []struct { + name string + data []byte + }{ + {"header only, no keys", valid[:blobStart]}, + {"truncated inside blob header", valid[:blobStart+2]}, + {"truncated inside blob body", valid[:blobStart+16]}, + {"zero blob length", mutate(blobStart, 0)}, + {"blob length below minimum", mutate(blobStart, 4)}, + {"blob length past end of data", mutate(blobStart, uint32(len(valid))+1)}, + {"keyblock offset out of range", mutate(blobStart+8, uint32(len(valid)))}, + {"keyblock length out of range", mutate(blobStart+12, uint32(len(valid)))}, + {"keyblock offset overflow", mutate(blobStart+8, ^uint32(0))}, + {"keyblock length overflow", mutate(blobStart+12, ^uint32(0))}, + {"openpgp blob shorter than its header", append(bytes.Clone(valid[:blobStart]), 0, 0, 0, 8, kbxBlobTypeOpenPGP, 1, 0, 0)}, + {"empty input", nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := keyboxPublicKeys(tt.data) + assert.Error(t, err) + }) + } +} diff --git a/pkg/provenance/sign.go b/pkg/provenance/sign.go index 90b7559e5..b4d5f7541 100644 --- a/pkg/provenance/sign.go +++ b/pkg/provenance/sign.go @@ -26,6 +26,7 @@ import ( "strings" "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/armor" "github.com/ProtonMail/go-crypto/openpgp/clearsign" "github.com/ProtonMail/go-crypto/openpgp/packet" "sigs.k8s.io/yaml" @@ -357,13 +358,64 @@ func loadKey(keypath string) (*openpgp.Entity, error) { return openpgp.ReadEntity(pr) } +// loadKeyRing loads a keyring from ringpath, auto-detecting its storage +// format. Three formats are supported: +// +// - the legacy binary OpenPGP packet stream (GnuPG's pubring.gpg) +// - a GnuPG file-backed keybox (pubring.kbx), introduced in GnuPG 2.1 +// - an ASCII-armored keyring, as produced by `gpg --export --armor` func loadKeyRing(ringpath string) (openpgp.EntityList, error) { - f, err := os.Open(ringpath) + data, err := os.ReadFile(ringpath) if err != nil { return nil, err } - defer f.Close() - return openpgp.ReadKeyRing(f) + switch { + case isKeybox(data): + keys, err := keyboxPublicKeys(data) + if err != nil { + return nil, fmt.Errorf("reading keybox %s: %w", ringpath, err) + } + return openpgp.ReadKeyRing(bytes.NewReader(keys)) + case isArmored(data): + ring, err := loadArmoredKeyRing(data) + if err != nil { + return nil, fmt.Errorf("reading armored keyring %s: %w", ringpath, err) + } + return ring, nil + default: + return openpgp.ReadKeyRing(bytes.NewReader(data)) + } +} + +// loadArmoredKeyRing reads every armored block in data and merges the keys +// into a single keyring. Unlike openpgp.ReadArmoredKeyRing, which silently +// ignores everything after the first block, this handles keyrings assembled +// by concatenating exports (cat key1.asc key2.asc > keyring.asc), the same +// way GnuPG imports them. +func loadArmoredKeyRing(data []byte) (openpgp.EntityList, error) { + var ring openpgp.EntityList + r := bytes.NewReader(data) + for { + block, err := armor.Decode(r) + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, err + } + if block.Type != openpgp.PublicKeyType && block.Type != openpgp.PrivateKeyType { + return nil, fmt.Errorf("expected a public or private key block, got %q", block.Type) + } + entities, err := openpgp.ReadKeyRing(block.Body) + if err != nil { + return nil, err + } + ring = append(ring, entities...) + } + if len(ring) == 0 { + return nil, errors.New("no keys found") + } + return ring, nil } // DigestFile calculates a SHA256 hash (like Docker) for a given file. diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go index ec6e6194b..f3e049999 100644 --- a/pkg/provenance/sign_test.go +++ b/pkg/provenance/sign_test.go @@ -16,6 +16,7 @@ limitations under the License. package provenance import ( + "bytes" "crypto" "errors" "io" @@ -24,6 +25,7 @@ import ( "strings" "testing" + "github.com/ProtonMail/go-crypto/openpgp/armor" pgperrors "github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/packet" "github.com/stretchr/testify/assert" @@ -143,6 +145,72 @@ func TestLoadKeyRing(t *testing.T) { } } +func TestLoadKeyRingKeybox(t *testing.T) { + k, err := loadKeyRing(testKeybox) + require.NoError(t, err) + + require.Len(t, k, 1) + _, ok := k[0].Identities[testKeyName] + assert.True(t, ok, "expected %q in keybox keyring", testKeyName) +} + +func TestLoadKeyRingMixedKeybox(t *testing.T) { + k, err := loadKeyRing(testMixedKeybox) + require.NoError(t, err) + + require.Len(t, k, 2) + + names := make([]string, 0, len(k)) + hasEdDSA := false + for _, e := range k { + for n := range e.Identities { + names = append(names, n) + } + if e.PrimaryKey != nil && e.PrimaryKey.PubKeyAlgo == packet.PubKeyAlgoEdDSA { + hasEdDSA = true + } + } + assert.Contains(t, names, testKeyName) + assert.True(t, hasEdDSA, "expected an Ed25519 key in %s", testMixedKeybox) +} + +func TestLoadKeyRingArmored(t *testing.T) { + k, err := loadKeyRing(testArmoredPubfile) + require.NoError(t, err) + + require.Len(t, k, 1) + _, ok := k[0].Identities[testKeyName] + assert.True(t, ok, "expected %q in armored keyring", testKeyName) +} + +func TestLoadKeyRingArmoredMultiBlock(t *testing.T) { + // A keyring assembled by concatenating exports (cat a.asc b.asc) must + // load every block, the same way gpg --import does. + k, err := loadKeyRing(testMultiBlockArmored) + require.NoError(t, err) + + require.Len(t, k, 2) + names := make([]string, 0, len(k)) + for _, e := range k { + for n := range e.Identities { + names = append(names, n) + } + } + assert.Contains(t, names, testKeyName) +} + +func TestLoadArmoredKeyRingRejectsNonKeyBlocks(t *testing.T) { + var buf bytes.Buffer + w, err := armor.Encode(&buf, "PGP MESSAGE", nil) + require.NoError(t, err) + _, err = w.Write([]byte("not a key")) + require.NoError(t, err) + require.NoError(t, w.Close()) + + _, err = loadArmoredKeyRing(buf.Bytes()) + assert.ErrorContains(t, err, "expected a public or private key block") +} + func TestDigest(t *testing.T) { f, err := os.Open(testChartfile) require.NoError(t, err) @@ -318,6 +386,31 @@ func TestVerify(t *testing.T) { } } +// TestVerifyKeyboxKeyring mirrors TestVerify with the keyring loaded from a +// GnuPG keybox instead of the legacy binary format. +func TestVerifyKeyboxKeyring(t *testing.T) { + signer, err := NewFromKeyring(testKeybox, "") + require.NoError(t, err) + + archiveData, err := os.ReadFile(testChartfile) + require.NoError(t, err) + + sigData, err := os.ReadFile(testSigBlock) + require.NoError(t, err) + + ver, err := signer.Verify(archiveData, sigData, filepath.Base(testChartfile)) + require.NoError(t, err) + require.NotNil(t, ver.SignedBy) + _, ok := ver.SignedBy.Identities[testKeyName] + assert.True(t, ok, "expected chart to verify as signed by %q", testKeyName) + + tamperedSigData, err := os.ReadFile(testTamperedSigBlock) + require.NoError(t, err) + + _, err = signer.Verify(archiveData, tamperedSigData, filepath.Base(testChartfile)) + assert.Error(t, err, "expected tampered signature to fail against keybox keyring") +} + // readSumFile reads a file containing a sum generated by the UNIX shasum tool. func readSumFile(sumfile string) (string, error) { data, err := os.ReadFile(sumfile) diff --git a/pkg/provenance/testdata/helm-mixed-keyring.asc b/pkg/provenance/testdata/helm-mixed-keyring.asc new file mode 100644 index 000000000..acde8ef18 --- /dev/null +++ b/pkg/provenance/testdata/helm-mixed-keyring.asc @@ -0,0 +1,40 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFeWbZ4BCADIsgNRyVBSWJkbH6h3UWWXsA9ce1u+YKvaCYFSjIujKycTAqCC +P7qUV2Oj/4E2zUcOD1/8/meDwuTnNTLzdSw4ujtFlKRSw7zelQE6sxvID0KM0pQK +7AxDTXsm/7Afd/fg4WNW0/hcbeiNz4TVmSWAnbqeLg8o8eljR5QhTk47H6Glo4hV +raeLCKG77qm2qOQ/m38ec+L5n9iUpoZZu1S5RXPUIanV5pLlx2rQsooIQEdJbTRF +Iv3+2Nj/56PFVdrw7E5ARqCD4PpzzYV7uj8vKumOp+VivFj95Ze6DFENh1WWy336 +jQEj0uTolgOYeQ6AdJobwSPUeYXGV6Sf2vwXABEBAAG0XUhlbG0gVGVzdGluZyAo +VGhpcyBrZXkgc2hvdWxkIG9ubHkgYmUgdXNlZCBmb3IgdGVzdGluZy4gRE8gTk9U +IFRSVVNULikgPGhlbG0tdGVzdGluZ0BoZWxtLnNoPokBNwQTAQoAIQUCV5ZtngIb +AwULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRCEO7+YH8GHYolFCAC0+ejT5dIX +0juRl5AdG6XAlEf9IrehkVlo0s5bG1Ucea9jjNRafmmHyMravrxZVI9zGFhaNpxA ++McnnWAO8lx5yTgnGW269qvFBsj5n0ItPewILQZjTEgwLYf1oc2qCFpk+8ovBnxZ +Kaz3J5QKqU09zLO2yALuI5FTCCUupJ3OGIrKvLJ4H2jsRk/5jgqIgqJotW/4Baza +4zemFdQHj9FPvq9sVeW5lyxM48i638GtslIgc0y2Yd/bfiy0tfIkskWz/kS1U6c9 +Utr3UPwpBqHjXnakmpK7GMGlp5eDXgr63BEdLKxJJWWxIys35fR19XZXcAELrwMh +zkTzB/dvI375uQENBFeWbZ4BCADB6a8oiF2royWbvwmujBxSmD68QWziNZbSCuRs +M4WM1iy69kspJHRwJEVIE5pkoVfV6tIcwQQ0VkJmq6Pf/pyKeMAXp0u8bp5K0mVE +Yio1/adiLM3kX1yleoXLNUb1gNvsk6Kp6BZCUNRmjKlbKxapJKHmL+opX8UoeLeE +KSgJNL8jvo3nRgg+wIPnVDoizz7y03F0k4SbgNGdoA/FtZs/vcSrEfzOLgI2RxQS +dj/ePkxg9TvQygUSYgAVbYlASw6XMmd/zTjkfD9qa0l3WSejTisn85b611WryDpA +iYkYC3GzK8g7S9VwkAbtr79cbG+1djlA4aHASpil+zR12yEhABEBAAGJAR8EGAEK +AAkFAleWbZ4CGwwACgkQhDu/mB/Bh2LbNQgAiOOMNzmKzY4A7/ra8ppaH9oL8XIa +WvInivOsx22K09PPuPVqi/ooBiRGyRqhdVS4ShPOoOTR5tOsdfPpEMTHnGQ1+jW+ +Tw5MXv3oMckWw3YEYptnuvon3wT4bOXLr+eYlY4Z1ONs+pAXwMiQ2zXNuKHpA2fR +HsF8Wyw57rCVn7K5nQgZCbVxasYlzvFGnmt/itSC5w/AnIvICDOWcoGFKTieqhME +IkCyvu+DNrMAumnD2fqF2olsM4IzCEPSMEQqJIGzZTtQTseS8NSDioRGnG1AJQ94 +BssQVmTh0/hlpoTqXY803lR5wb1fr3RRnkOu+lbEI6AAUj51j5TAAo/1dA== +=bvcV +-----END PGP PUBLIC KEY BLOCK----- +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEaMzP2xYJKwYBBAHaRw8BAQdAXkL0KKTOZoMaM+6HsrkWv93zmLmnMiIoQvR2 +F+bjx7G0KEhlbG0gRWQyNTUxOSBUZXN0IDxoZWxtLWVkMjU1MTlAaGVsbS5zaD6I +mQQTFgoAQRYhBEy+dUwlWAxsh43nEfuFaBLOL8tkBQJozM/bAhsDBQkFo5qABQsJ +CAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEPuFaBLOL8tktBYA/2cclE0N+BZV +Wymk6XfwFMjErM6olWAfwA8DfAGQ55+lAQCqSa+Tkjd4dmToVHcb0QAm+zwklAY4 +e6qWgGiivn77AA== +=wJ7K +-----END PGP PUBLIC KEY BLOCK----- diff --git a/pkg/provenance/testdata/helm-mixed-keyring.kbx b/pkg/provenance/testdata/helm-mixed-keyring.kbx new file mode 100644 index 0000000000000000000000000000000000000000..300711cdb504797ac7462b06a4a8e692b6ee1793 GIT binary patch literal 1865 zcmZ{k2~-nz9>-@U2T4Gpq5*|MxJnSjRtTUdqzFL-K_HP+ppZboAZQ3jaS;Uyi72R% z`-%Y(p$!e95|Lxo5~aiv;;tKZJ<9E4t%6htqD=z4-Phgz-kbmYt~c|a|L;2=008hH z2tYpWA#s4p63M%x)R)>{F+>FfYP@U!KxhFFU=;av5T|30;FRu!_Hdp{Pq|57L5!*c zukPAlW=3bQT3yvoWxUo1jJTB9hxm+kK z`OL7fm*=%?c8h-@lp&Lb8&Zujg|S8QumRgTE#>D-9jdHytB;K zcT^&N-uIm-5YO_x`StUWY}*?hvlb%H_KzMNK}~xD74HvyutxGnBhwlxFWxceYiugZ zjl?Mbx#2V0R&PuC59T&bX~8L=wh(42^_bIopKPAJ*oFjPs-Xd${}FTFw2)AE zq(xv^)r0=N$SAPfwd*V9FT_$AW{hc{U-UOy@ozrX7O5!~p8NGGY5d?PuVhAjaEHV0 zaV>)9AFH_;z4}c)UB_w<$mkw1+lb3eF}8zaQ4x*bA>HN2HMu;e&aJ0y27kBVH@T*cWTN!4e(0+SjYuKz6cdoTRi90;}`1X9_StS8! zL4LTE&!*fypeL;#8=4qyWzS8ozu#X?BPxmA{<^-A%Tr%I#NA7S$5h3Ol~&K;3(WBW z;bi&c^IPPjOl66FucTyzICz^s4T~Q#?mHQ7yX$q^jaKa}|j4%7aOJ!+rq3y*CUr`zc+ zhy404zLDp0^T^dnBui`ta$x;o+RNbu`qjKwM~WSuQ8N4PMhQ~`YWIDwJbd509td=0 z7xQ|d;`!7y07EIH&)2m#p4! literal 0 HcmV?d00001 diff --git a/pkg/provenance/testdata/helm-test-key.asc b/pkg/provenance/testdata/helm-test-key.asc new file mode 100644 index 000000000..2d069d9b7 --- /dev/null +++ b/pkg/provenance/testdata/helm-test-key.asc @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFeWbZ4BCADIsgNRyVBSWJkbH6h3UWWXsA9ce1u+YKvaCYFSjIujKycTAqCC +P7qUV2Oj/4E2zUcOD1/8/meDwuTnNTLzdSw4ujtFlKRSw7zelQE6sxvID0KM0pQK +7AxDTXsm/7Afd/fg4WNW0/hcbeiNz4TVmSWAnbqeLg8o8eljR5QhTk47H6Glo4hV +raeLCKG77qm2qOQ/m38ec+L5n9iUpoZZu1S5RXPUIanV5pLlx2rQsooIQEdJbTRF +Iv3+2Nj/56PFVdrw7E5ARqCD4PpzzYV7uj8vKumOp+VivFj95Ze6DFENh1WWy336 +jQEj0uTolgOYeQ6AdJobwSPUeYXGV6Sf2vwXABEBAAG0XUhlbG0gVGVzdGluZyAo +VGhpcyBrZXkgc2hvdWxkIG9ubHkgYmUgdXNlZCBmb3IgdGVzdGluZy4gRE8gTk9U +IFRSVVNULikgPGhlbG0tdGVzdGluZ0BoZWxtLnNoPokBNwQTAQoAIQUCV5ZtngIb +AwULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRCEO7+YH8GHYolFCAC0+ejT5dIX +0juRl5AdG6XAlEf9IrehkVlo0s5bG1Ucea9jjNRafmmHyMravrxZVI9zGFhaNpxA ++McnnWAO8lx5yTgnGW269qvFBsj5n0ItPewILQZjTEgwLYf1oc2qCFpk+8ovBnxZ +Kaz3J5QKqU09zLO2yALuI5FTCCUupJ3OGIrKvLJ4H2jsRk/5jgqIgqJotW/4Baza +4zemFdQHj9FPvq9sVeW5lyxM48i638GtslIgc0y2Yd/bfiy0tfIkskWz/kS1U6c9 +Utr3UPwpBqHjXnakmpK7GMGlp5eDXgr63BEdLKxJJWWxIys35fR19XZXcAELrwMh +zkTzB/dvI375uQENBFeWbZ4BCADB6a8oiF2royWbvwmujBxSmD68QWziNZbSCuRs +M4WM1iy69kspJHRwJEVIE5pkoVfV6tIcwQQ0VkJmq6Pf/pyKeMAXp0u8bp5K0mVE +Yio1/adiLM3kX1yleoXLNUb1gNvsk6Kp6BZCUNRmjKlbKxapJKHmL+opX8UoeLeE +KSgJNL8jvo3nRgg+wIPnVDoizz7y03F0k4SbgNGdoA/FtZs/vcSrEfzOLgI2RxQS +dj/ePkxg9TvQygUSYgAVbYlASw6XMmd/zTjkfD9qa0l3WSejTisn85b611WryDpA +iYkYC3GzK8g7S9VwkAbtr79cbG+1djlA4aHASpil+zR12yEhABEBAAGJAR8EGAEK +AAkFAleWbZ4CGwwACgkQhDu/mB/Bh2LbNQgAiOOMNzmKzY4A7/ra8ppaH9oL8XIa +WvInivOsx22K09PPuPVqi/ooBiRGyRqhdVS4ShPOoOTR5tOsdfPpEMTHnGQ1+jW+ +Tw5MXv3oMckWw3YEYptnuvon3wT4bOXLr+eYlY4Z1ONs+pAXwMiQ2zXNuKHpA2fR +HsF8Wyw57rCVn7K5nQgZCbVxasYlzvFGnmt/itSC5w/AnIvICDOWcoGFKTieqhME +IkCyvu+DNrMAumnD2fqF2olsM4IzCEPSMEQqJIGzZTtQTseS8NSDioRGnG1AJQ94 +BssQVmTh0/hlpoTqXY803lR5wb1fr3RRnkOu+lbEI6AAUj51j5TAAo/1dA== +=bvcV +-----END PGP PUBLIC KEY BLOCK----- diff --git a/pkg/provenance/testdata/helm-test-key.kbx b/pkg/provenance/testdata/helm-test-key.kbx new file mode 100644 index 0000000000000000000000000000000000000000..f2bf73fa5d6400c692de896244824df499a7f568 GIT binary patch literal 1465 zcmZQzU{GLWWMJ}kib!Jsf-Ea$H2hK&cMVV6PFm=xz%QIJ>N6# z%PrRXXUHFHPXek?0I8DNp3`8TT+PjXaXLHahJ>Drs!!d3!Zsi|AYf$R0TT0otXMFg ziGc-7qY*F{%w*(c37?ibkCB7n#3tszlL0{yGo|HMln16x-@qSJ9lbAM^)1fEpq}o< z+Umkg3!3bAO$kq4{J+uctUDin{GWg6&4->mH#PcPs$;Rs+I7m3pu>CaO=Yy&EPaCC zsprxZt~Wf+zSU~~H^`TNfABCl?DCJ8+!wv)TdvMjZJ4`jo*uu($Ct_OQxyIDtmPLj zUEC46c6m3)!rkvyZd>uhes;ZF@uQ#fZ%kR%7P&iQr)%*Q#g$i|O?rAf>%yik4hMJ7 zToYHNzyEIB`2T$I(a>8T-uOAVEogr5tN3hd^)7pTt(SevpC;{z`1^GFE}lT%_Rwjk zYk&1JDqni?VjA;|O1_4YS<(lUuT-`k3tuw-)*o>OK}H6~4ZzS#FGz>P2g8pm z{}6?cpwQqDJxv9h44^r>P+12MN3S@;4r&<_ByKtx%~^yQxfm2#nLshZB+bmq&B?*e z%qq&o$-&Jk#>5OvMRJTx;tdR3oC2_9(do*;u;u5A%TF(fU$UM!eS)m?(gRc6|0-=? zI59Hg(z$5qP?^g0$vs!1>N49;oVvAdPh?1cu|!0a*&K%-$JOU1@O_G@JZYgWnY-)T z>Z5EYe$IE&wSB{(%a-ipVW8Xob>Z1n98oF1PwBJOL~5@2u0DlprLXOo&D%~ey;GhT z%%Q5cWbQeMu2Xw9Rmf+&ar6J#$JNoaC}V5>57sre9-A){y~5sq(SP6ioY1E`r|bAU zKC$cm!L^%$6pDSeCEmYXr?X}2CzVaEoBz3N4PI^=bnAP-A5FG}kK@Xg%$l@Y;^5Nd z)0^YCe%%q2)mh`Inz~V0+x+R5(ywLV1&rM5nHA5ud}jZiuUz+Y0~-S~Fwz)yA`_)& zBy*6(B+Myo_2T>g=5$pY5MS=SCvTqDrBs(BEz`ftlXT8LiH})Y)q2|0?Q6sBH zWp=~GxeNG@Zk=tv_sD9&Kj-wA%-ltU%Ixpi`6PU`zHo|FD2YKdx6{F!Z@N)>{aK4A zHTGHAp5>A1i~Y3KKTrF0J#_U6D~HZb3GTwp+9#~NuNF*Td%J#rOiupRGE0Yt3lDhB zSo+(f^tPfRI5BrJ%CkrSQxqpGMe!h~oZF@x3>}Yq%q_dl_A$Kwb?eitDEV95AB&`- zKB;$oUUNLR>+{@dGC&+%`SCW8q8Y^ow!_ zYoc{5-))#Wf78yn9Fm+{3$u=?p8M!FFT1|$O4D=x19Q4ha2QW3YHZcCn72xpMaf~) zzW2>$n;CXx9=`dj^;Tz&ag#BJ^CbfpEtSU2sn!90$0vQb(%jYJHYe9XmA``Rv_M$O z!^=NXm$kf#?Kim>Qh9K1{Q8o>dCu#8g&k2|zz}3t+CSw0Q~%czSju4&c@tW2kz2dq Q!NQf1U03#IY|5+y07Bqtg8%>k literal 0 HcmV?d00001 diff --git a/pkg/provenance/testdata/regen-keyring-formats.sh b/pkg/provenance/testdata/regen-keyring-formats.sh new file mode 100755 index 000000000..c9cc95fc6 --- /dev/null +++ b/pkg/provenance/testdata/regen-keyring-formats.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# Regenerate the file-backed keyring fixtures from the committed binary +# keyrings. Requires GnuPG 2.1+ (keybox support). +# +# helm-test-key.kbx keybox (pubring.kbx) containing the helm-test key +# helm-mixed-keyring.kbx keybox containing the RSA and Ed25519 test keys +# helm-test-key.asc ASCII-armored export of the helm-test key +# helm-mixed-keyring.asc two concatenated single-key armored exports +set -e + +GNUPGHOME=$(mktemp -d) +export GNUPGHOME +chmod 700 "$GNUPGHOME" +gpg --batch --no-tty --quiet --import helm-test-key.pub +cp "$GNUPGHOME/pubring.kbx" helm-test-key.kbx +gpg --batch --no-tty --export --armor helm-testing@helm.sh > helm-test-key.asc +rm -rf "$GNUPGHOME" + +GNUPGHOME=$(mktemp -d) +export GNUPGHOME +chmod 700 "$GNUPGHOME" +gpg --batch --no-tty --quiet --import helm-mixed-keyring.pub +cp "$GNUPGHOME/pubring.kbx" helm-mixed-keyring.kbx +gpg --batch --no-tty --export --armor helm-testing@helm.sh > helm-mixed-keyring.asc +gpg --batch --no-tty --export --armor helm-ed25519@helm.sh >> helm-mixed-keyring.asc +rm -rf "$GNUPGHOME" From 6888b0a280bb6fbb847c85d2a473c425b6a0862d Mon Sep 17 00:00:00 2001 From: MsfPablo Date: Mon, 24 Aug 2026 21:03:31 +0200 Subject: [PATCH 534/571] fix: correct 'doest not match' in readiness debug logs Signed-off-by: MsfPablo --- internal/plugin/plugin_type_registry.go | 2 +- pkg/kube/ready.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/plugin/plugin_type_registry.go b/internal/plugin/plugin_type_registry.go index 5138422bd..78bd38e0f 100644 --- a/internal/plugin/plugin_type_registry.go +++ b/internal/plugin/plugin_type_registry.go @@ -16,7 +16,7 @@ limitations under the License. /* This file contains a "registry" of supported plugin types. -It enables "dyanmic" operations on the go type associated with a given plugin type (see: `helm.sh/helm/v4/internal/plugin/schema` package) +It enables "dynamic" operations on the go type associated with a given plugin type (see: `helm.sh/helm/v4/internal/plugin/schema` package) Examples: diff --git a/pkg/kube/ready.go b/pkg/kube/ready.go index 196fe922a..9beb35665 100644 --- a/pkg/kube/ready.go +++ b/pkg/kube/ready.go @@ -386,7 +386,7 @@ func (c *ReadyChecker) crdReady(crd apiextv1.CustomResourceDefinition) bool { func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool { // Verify the generation observed by the statefulSet controller matches the spec generation if sts.Status.ObservedGeneration != sts.Generation { - slog.Debug("StatefulSet is not ready, observedGeneration doest not match spec generation", "namespace", sts.GetNamespace(), "name", sts.GetName(), "actualGeneration", sts.Status.ObservedGeneration, "expectedGeneration", sts.Generation) + slog.Debug("StatefulSet is not ready, observedGeneration does not match spec generation", "namespace", sts.GetNamespace(), "name", sts.GetName(), "actualGeneration", sts.Status.ObservedGeneration, "expectedGeneration", sts.Generation) return false } @@ -440,7 +440,7 @@ func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool { func (c *ReadyChecker) replicationControllerReady(rc *corev1.ReplicationController) bool { // Verify the generation observed by the replicationController controller matches the spec generation if rc.Status.ObservedGeneration != rc.Generation { - slog.Debug("ReplicationController is not ready, observedGeneration doest not match spec generation", "namespace", rc.GetNamespace(), "name", rc.GetName(), "actualGeneration", rc.Status.ObservedGeneration, "expectedGeneration", rc.Generation) + slog.Debug("ReplicationController is not ready, observedGeneration does not match spec generation", "namespace", rc.GetNamespace(), "name", rc.GetName(), "actualGeneration", rc.Status.ObservedGeneration, "expectedGeneration", rc.Generation) return false } return true @@ -449,7 +449,7 @@ func (c *ReadyChecker) replicationControllerReady(rc *corev1.ReplicationControll func (c *ReadyChecker) replicaSetReady(rs *appsv1.ReplicaSet) bool { // Verify the generation observed by the replicaSet controller matches the spec generation if rs.Status.ObservedGeneration != rs.Generation { - slog.Debug("ReplicaSet is not ready, observedGeneration doest not match spec generation", "namespace", rs.GetNamespace(), "name", rs.GetName(), "actualGeneration", rs.Status.ObservedGeneration, "expectedGeneration", rs.Generation) + slog.Debug("ReplicaSet is not ready, observedGeneration does not match spec generation", "namespace", rs.GetNamespace(), "name", rs.GetName(), "actualGeneration", rs.Status.ObservedGeneration, "expectedGeneration", rs.Generation) return false } return true From fb1930096eacec13ac911b8499905fc94eb09a8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 05:30:42 -0600 Subject: [PATCH 535/571] chore(deps): bump the k8s-io group with 7 updates (#32572) Bumps the k8s-io group with 7 updates: | Package | From | To | | --- | --- | --- | | [k8s.io/api](https://github.com/kubernetes/api) | `0.36.3` | `0.36.4` | | [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) | `0.36.3` | `0.36.4` | | [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) | `0.36.3` | `0.36.4` | | [k8s.io/apiserver](https://github.com/kubernetes/apiserver) | `0.36.3` | `0.36.4` | | [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime) | `0.36.3` | `0.36.4` | | [k8s.io/client-go](https://github.com/kubernetes/client-go) | `0.36.3` | `0.36.4` | | [k8s.io/kubectl](https://github.com/kubernetes/kubectl) | `0.36.3` | `0.36.4` | Updates `k8s.io/api` from 0.36.3 to 0.36.4 - [Commits](https://github.com/kubernetes/api/compare/v0.36.3...v0.36.4) Updates `k8s.io/apiextensions-apiserver` from 0.36.3 to 0.36.4 - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.36.3...v0.36.4) Updates `k8s.io/apimachinery` from 0.36.3 to 0.36.4 - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.36.3...v0.36.4) Updates `k8s.io/apiserver` from 0.36.3 to 0.36.4 - [Commits](https://github.com/kubernetes/apiserver/compare/v0.36.3...v0.36.4) Updates `k8s.io/cli-runtime` from 0.36.3 to 0.36.4 - [Commits](https://github.com/kubernetes/cli-runtime/compare/v0.36.3...v0.36.4) Updates `k8s.io/client-go` from 0.36.3 to 0.36.4 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.36.3...v0.36.4) Updates `k8s.io/kubectl` from 0.36.3 to 0.36.4 - [Commits](https://github.com/kubernetes/kubectl/compare/v0.36.3...v0.36.4) --- updated-dependencies: - dependency-name: k8s.io/api dependency-version: 0.36.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apiextensions-apiserver dependency-version: 0.36.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apimachinery dependency-version: 0.36.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apiserver dependency-version: 0.36.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/cli-runtime dependency-version: 0.36.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/client-go dependency-version: 0.36.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/kubectl dependency-version: 0.36.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index c34a80649..bf8bb7228 100644 --- a/go.mod +++ b/go.mod @@ -39,14 +39,14 @@ require ( golang.org/x/term v0.45.0 golang.org/x/text v0.41.0 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.36.3 - k8s.io/apiextensions-apiserver v0.36.3 - k8s.io/apimachinery v0.36.3 - k8s.io/apiserver v0.36.3 - k8s.io/cli-runtime v0.36.3 - k8s.io/client-go v0.36.3 + k8s.io/api v0.36.4 + k8s.io/apiextensions-apiserver v0.36.4 + k8s.io/apimachinery v0.36.4 + k8s.io/apiserver v0.36.4 + k8s.io/cli-runtime v0.36.4 + k8s.io/client-go v0.36.4 k8s.io/klog/v2 v2.140.0 - k8s.io/kubectl v0.36.3 + k8s.io/kubectl v0.36.4 oras.land/oras-go/v2 v2.6.2 sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/kustomize/kyaml v0.21.1 @@ -172,7 +172,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/component-base v0.36.3 // indirect + k8s.io/component-base v0.36.4 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect diff --git a/go.sum b/go.sum index bfac195f1..b9d8e51fc 100644 --- a/go.sum +++ b/go.sum @@ -490,26 +490,26 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.36.3 h1:NxB+05W2UGqXWFXcLO0RB5cnqnUPP5v5sVlaOH0Iz4w= -k8s.io/api v0.36.3/go.mod h1:JzLQKqRHC5+I8RVj/lS3lCg0mg6nWI9Fo/Sk3ElxHzg= -k8s.io/apiextensions-apiserver v0.36.3 h1:dPmOAPhwTtqb1bTxbFPsy18KHPhktQeO3WUPXunZIB0= -k8s.io/apiextensions-apiserver v0.36.3/go.mod h1:KTXFqgXiuw2pRoL+Wpmttqc+up9Xt/GohadPWeLLOa4= -k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM= -k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE= -k8s.io/apiserver v0.36.3 h1:MGSg2SkdfuytiDEcRylT5mQFmmSsbx90XFUO67Y4bsQ= -k8s.io/apiserver v0.36.3/go.mod h1:fVH7zv9EUNUA7Fl7LtDKh8aB9W7u1VQPSGtWV5SjUxg= -k8s.io/cli-runtime v0.36.3 h1:g+eJ+M1sYpnNYp/q5fzaw2KejIL0Q7DH+xFl6YVoL4U= -k8s.io/cli-runtime v0.36.3/go.mod h1:hZpAqK8nSFXvvLaVCbzUPVp8e9TRLSTCfpNzMt7s3tE= -k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg= -k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30= -k8s.io/component-base v0.36.3 h1:vc/UFvPCkW0irPz84LAodAL1j3f4xktPM6dDJIEheAY= -k8s.io/component-base v0.36.3/go.mod h1:hZbNFG+gCMl9EbykDGEu73feKP9/Cq6JsV4pTo9GTO8= +k8s.io/api v0.36.4 h1:RxrvqCL6vgH5/+UnTeu1IIFqYmGfy0hnyrod1rn35Oo= +k8s.io/api v0.36.4/go.mod h1:S2B3orCFBDhrgyWbLeuKcT2QdHIpQesBkCYSlWtwUOw= +k8s.io/apiextensions-apiserver v0.36.4 h1:SfvCVt+4CqKWvzuVytYDT5g9hyb9MztoiYELIkPVrFc= +k8s.io/apiextensions-apiserver v0.36.4/go.mod h1:JT9V2Ju7ys1FY4zbSpmX9XOvKB3/BwsODc4hFQEa+Xo= +k8s.io/apimachinery v0.36.4 h1:PT2UzkupGuAx/+xT5XjiMJ1WGpY3fn9/hdAvjweRet4= +k8s.io/apimachinery v0.36.4/go.mod h1:p2I2dipt7JHG+quVwQ1d02d28O4GdDi77RByQ13MTpk= +k8s.io/apiserver v0.36.4 h1:AtKjaf2eUiX5G6TfF2IOlhuUuvMsHh49Ivr1+4fZ2gA= +k8s.io/apiserver v0.36.4/go.mod h1:RyiGghXP67hb0Ll+7iLJ6GGv2JpEzCn7ljbiA+L3cJ0= +k8s.io/cli-runtime v0.36.4 h1:OHvManCwP1k9GiC5tXRFxHhzZIQQFCsrHlt7OspKo3w= +k8s.io/cli-runtime v0.36.4/go.mod h1:qQSj2FJgQos6GHpS/ge7wTdQMZm9XFWlesWgV6h7qZY= +k8s.io/client-go v0.36.4 h1:MDvfDNvMSt0Br94SK8neviVlwL9qifw9B26hJCpD1K0= +k8s.io/client-go v0.36.4/go.mod h1:pNK4WKELbwlEDvtbE8l22lEZL5THYF61H5EealokZmA= +k8s.io/component-base v0.36.4 h1:tz75yC2xgq3kd7vPdBtR8do5iMx0OHf6Zd1kuaxDB84= +k8s.io/component-base v0.36.4/go.mod h1:DCwb306U8ou89NNAp45Csuy8ok+1rp1ELDVPhzN5AWc= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/kubectl v0.36.3 h1:TesKp+XYQEjPYoFvuobcVnuvira2+/xAVlq//+kksaI= -k8s.io/kubectl v0.36.3/go.mod h1:W+NEb1CzBGmoaI1Nrpn2ETo9omNBl0AsyxnnMT40N6E= +k8s.io/kubectl v0.36.4 h1:xZd9g1bFBd7hpb1oKjK8lT9jRL18dtgr4DAPQG1Oksk= +k8s.io/kubectl v0.36.4/go.mod h1:STWlr78cdEa1hHpr55wpcboaqchvfDueKRNDa1zOd1w= k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc= k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= From 0f4decb96b1c4ac45981f495d45eef6cc82e3192 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 05:43:03 -0600 Subject: [PATCH 536/571] chore(deps): bump the github-actions group across 1 directory with 4 updates (#32574) Bumps the github-actions group with 4 updates in the / directory: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action). Updates `github/codeql-action/init` from 4.37.7 to 4.37.8 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28) Updates `github/codeql-action/autobuild` from 4.37.7 to 4.37.8 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28) Updates `github/codeql-action/analyze` from 4.37.7 to 4.37.8 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28) Updates `github/codeql-action/upload-sarif` from 4.37.7 to 4.37.8 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28) --- updated-dependencies: - dependency-name: github/codeql-action/analyze dependency-version: 4.37.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/init dependency-version: 4.37.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.37.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 6bfec7108..d4d9f5228 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # pinv4.37.7 + uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # pinv4.37.8 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # pinv4.37.7 + uses: github/codeql-action/autobuild@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # pinv4.37.8 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # pinv4.37.7 + uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # pinv4.37.8 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 0a12abf49..6d5367952 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 + uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 with: sarif_file: results.sarif From 6d1f67cf6a9918ebe6c41f05096b6fa3fdde1b5b Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Tue, 25 Aug 2026 15:09:30 -0400 Subject: [PATCH 537/571] Updating the Go version This is needed for goreleaser to create updates. Its latest release needs it. Signed-off-by: Matt Farina --- .github/env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/env b/.github/env index 9a0c1ef4c..eb1fa0ee8 100644 --- a/.github/env +++ b/.github/env @@ -1,2 +1,2 @@ -GOLANG_VERSION=1.26 -GOLANGCI_LINT_VERSION=v2.12.2 +GOLANG_VERSION=1.27 +GOLANGCI_LINT_VERSION=v2.13.1 From b5b498b77403f93de037cba383f424953e87eaaa Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Tue, 25 Aug 2026 15:09:30 -0400 Subject: [PATCH 538/571] Updating the Go version This is needed for goreleaser to create updates. Its latest release needs it. Updates needed for linting and to fix failed tests resulting from Go std library changes. Signed-off-by: Matt Farina --- cmd/helm/helm.go | 3 +-- internal/chart/v3/loader/load.go | 2 +- internal/chart/v3/util/dependencies.go | 3 +-- internal/chart/v3/util/doc.go | 2 +- internal/chart/v3/util/save_test.go | 26 ++++++++++--------- internal/plugin/config_test.go | 2 +- internal/plugin/runtime_subprocess.go | 8 +++--- internal/plugin/runtime_subprocess_getter.go | 2 +- .../deployment/util/deploymentutil.go | 4 +-- internal/version/clientgo_test.go | 14 +++++++--- pkg/chart/v2/loader/load.go | 2 +- pkg/chart/v2/util/dependencies.go | 3 +-- pkg/chart/v2/util/doc.go | 2 +- pkg/chart/v2/util/save_test.go | 26 ++++++++++--------- pkg/cmd/dependency_build.go | 3 +-- pkg/engine/engine.go | 3 +-- pkg/getter/httpgetter_test.go | 10 +++---- pkg/kube/client_test.go | 1 - pkg/registry/client.go | 7 ++++- pkg/repo/v1/repotest/server.go | 13 ++++++++++ 20 files changed, 78 insertions(+), 58 deletions(-) diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 60df8a308..42442d392 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -42,8 +42,7 @@ func main() { } if err := cmd.Execute(); err != nil { - var cerr helmcmd.CommandError - if errors.As(err, &cerr) { + if cerr, ok := errors.AsType[helmcmd.CommandError](err); ok { os.Exit(cerr.ExitCode) } os.Exit(1) diff --git a/internal/chart/v3/loader/load.go b/internal/chart/v3/loader/load.go index 0ff040eae..72343d7b6 100644 --- a/internal/chart/v3/loader/load.go +++ b/internal/chart/v3/loader/load.go @@ -123,7 +123,7 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { } fname := strings.TrimPrefix(f.Name, "charts/") - cname := strings.SplitN(fname, "/", 2)[0] + cname, _, _ := strings.Cut(fname, "/") if slices.Index(subChartsKeys, cname) == -1 { subChartsKeys = append(subChartsKeys, cname) } diff --git a/internal/chart/v3/util/dependencies.go b/internal/chart/v3/util/dependencies.go index e1b3e0b97..8b84ef428 100644 --- a/internal/chart/v3/util/dependencies.go +++ b/internal/chart/v3/util/dependencies.go @@ -46,7 +46,6 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values, if c != "" { // retrieve value vv, err := cvals.PathValue(cpath + c) - var errNoValue common.ErrNoValue if err == nil { // if not bool, warn if bv, ok := vv.(bool); ok { @@ -54,7 +53,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values, break } slog.Warn("returned non-bool value", "path", c, "chart", r.Name) - } else if errors.As(err, &errNoValue) { + } else if _, ok := errors.AsType[common.ErrNoValue](err); ok { // this is a real error slog.Warn("the method PathValue returned error", slog.Any("error", err)) } diff --git a/internal/chart/v3/util/doc.go b/internal/chart/v3/util/doc.go index dc5a07462..70dc7199f 100644 --- a/internal/chart/v3/util/doc.go +++ b/internal/chart/v3/util/doc.go @@ -42,4 +42,4 @@ into a Chart. When creating charts in memory, use the 'helm.sh/helm/pkg/chart' package directly. */ -package util // import "helm.sh/helm/v4/internal/chart/v3/util" +package util diff --git a/internal/chart/v3/util/save_test.go b/internal/chart/v3/util/save_test.go index 26bb54019..00f2c5cf4 100644 --- a/internal/chart/v3/util/save_test.go +++ b/internal/chart/v3/util/save_test.go @@ -231,13 +231,10 @@ func TestSaveDir(t *testing.T) { } func TestRepeatableSave(t *testing.T) { - tmp := t.TempDir() - defer os.RemoveAll(tmp) modTime := time.Date(2021, 9, 1, 20, 34, 58, 651387237, time.UTC) tests := []struct { name string chart *chart.Chart - want string }{ { name: "Package 1 file", @@ -258,7 +255,6 @@ func TestRepeatableSave(t *testing.T) { Schema: []byte("{\n \"title\": \"Values\"\n}"), SchemaModTime: modTime, }, - want: "5bfea18cc3c8cbc265744bc32bffa9489a4dbe87d6b51b90f4255e4839d35e03", }, { name: "Package 2 files", @@ -280,20 +276,26 @@ func TestRepeatableSave(t *testing.T) { Schema: []byte("{\n \"title\": \"Values\"\n}"), SchemaModTime: modTime, }, - want: "a240365c21e0a2f4a57873132a9b686566a612d08bcb3f20c9446bfff005ccce", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - // create package - dest := path.Join(tmp, "newdir") - where, err := Save(test.chart, dest) + // Package the same chart twice, into separate directories so the + // second Save does not simply overwrite the first. + first, err := Save(test.chart, t.TempDir()) + require.NoError(t, err, "Failed to save") + second, err := Save(test.chart, t.TempDir()) require.NoError(t, err, "Failed to save") - // get shasum for package - result, err := sha256Sum(where) + + firstSum, err := sha256Sum(first) require.NoError(t, err, "Failed to check shasum") - // assert that the package SHA is what we wanted. - assert.Equal(t, test.want, result, "FormatName() result = %v, want %v", result, test.want) + secondSum, err := sha256Sum(second) + require.NoError(t, err, "Failed to check shasum") + + // Packaging a chart must be reproducible. The digest itself is not + // pinned here because the bytes depend on the compression + // implementation of the Go release Helm is built with. + assert.Equal(t, firstSum, secondSum, "Save() is not repeatable") }) } } diff --git a/internal/plugin/config_test.go b/internal/plugin/config_test.go index ec81ffed6..ca7b26773 100644 --- a/internal/plugin/config_test.go +++ b/internal/plugin/config_test.go @@ -41,7 +41,7 @@ func TestUnmarshaConfig(t *testing.T) { ShortHelp: "short help string", LongHelp: "long help string", IgnoreFlags: true, - }, *(config.(*schema.ConfigCLIV1))) + }, *config.(*schema.ConfigCLIV1)) } // Test unmarshalling invalid config data diff --git a/internal/plugin/runtime_subprocess.go b/internal/plugin/runtime_subprocess.go index 2dbe220d9..9ea69e116 100644 --- a/internal/plugin/runtime_subprocess.go +++ b/internal/plugin/runtime_subprocess.go @@ -74,7 +74,7 @@ func (r *RuntimeSubprocess) CreatePlugin(pluginDir string, metadata *Metadata) ( return &SubprocessPluginRuntime{ metadata: *metadata, pluginDir: pluginDir, - RuntimeConfig: *(metadata.RuntimeConfig.(*RuntimeConfigSubprocess)), + RuntimeConfig: *metadata.RuntimeConfig.(*RuntimeConfigSubprocess), EnvVars: maps.Clone(r.EnvVars), }, nil } @@ -153,8 +153,7 @@ func (r *SubprocessPluginRuntime) InvokeHook(event string) error { slog.Debug("executing plugin hook command", slog.String("pluginName", r.metadata.Name), slog.String("command", cmd.String())) if err := cmd.Run(); err != nil { - var eerr *exec.ExitError - if errors.As(err, &eerr) { + if eerr, ok := errors.AsType[*exec.ExitError](err); ok { os.Stderr.Write(eerr.Stderr) return fmt.Errorf("plugin %s hook for %q exited with error", event, r.metadata.Name) } @@ -168,8 +167,7 @@ func (r *SubprocessPluginRuntime) InvokeHook(event string) error { // then replace the other three with a call to this func func executeCmd(prog *exec.Cmd, pluginName string) error { if err := prog.Run(); err != nil { - var eerr *exec.ExitError - if errors.As(err, &eerr) { + if eerr, ok := errors.AsType[*exec.ExitError](err); ok { slog.Debug( "plugin execution failed", slog.String("pluginName", pluginName), diff --git a/internal/plugin/runtime_subprocess_getter.go b/internal/plugin/runtime_subprocess_getter.go index 3f07fa764..67feab94f 100644 --- a/internal/plugin/runtime_subprocess_getter.go +++ b/internal/plugin/runtime_subprocess_getter.go @@ -42,7 +42,7 @@ func getProtocolCommand(commands []SubprocessProtocolCommand, protocol string) * // TODO can we replace a lot of this func with RuntimeSubprocess.invokeWithEnv? func (r *SubprocessPluginRuntime) runGetter(ctx context.Context, input *Input) (*Output, error) { - msg, ok := (input.Message).(schema.InputMessageGetterV1) + msg, ok := input.Message.(schema.InputMessageGetterV1) if !ok { return nil, fmt.Errorf("expected input type schema.InputMessageGetterV1, got %T", input) } diff --git a/internal/third_party/k8s.io/kubernetes/deployment/util/deploymentutil.go b/internal/third_party/k8s.io/kubernetes/deployment/util/deploymentutil.go index ae62d0e6f..666e1be40 100644 --- a/internal/third_party/k8s.io/kubernetes/deployment/util/deploymentutil.go +++ b/internal/third_party/k8s.io/kubernetes/deployment/util/deploymentutil.go @@ -136,11 +136,11 @@ func IsRollingUpdate(deployment *apps.Deployment) bool { // MaxUnavailable returns the maximum unavailable pods a rolling deployment can take. func MaxUnavailable(deployment apps.Deployment) int32 { - if !IsRollingUpdate(&deployment) || *(deployment.Spec.Replicas) == 0 { + if !IsRollingUpdate(&deployment) || *deployment.Spec.Replicas == 0 { return int32(0) } // Error caught by validation - _, maxUnavailable, _ := ResolveFenceposts(deployment.Spec.Strategy.RollingUpdate.MaxSurge, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, *(deployment.Spec.Replicas)) + _, maxUnavailable, _ := ResolveFenceposts(deployment.Spec.Strategy.RollingUpdate.MaxSurge, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, *deployment.Spec.Replicas) if maxUnavailable > *deployment.Spec.Replicas { return *deployment.Spec.Replicas } diff --git a/internal/version/clientgo_test.go b/internal/version/clientgo_test.go index 624c669af..2304306e0 100644 --- a/internal/version/clientgo_test.go +++ b/internal/version/clientgo_test.go @@ -17,14 +17,20 @@ limitations under the License. package version import ( + "strings" "testing" "github.com/stretchr/testify/require" ) func TestK8sClientGoModVersion(t *testing.T) { - // Unfortunately, test builds don't include debug info / module info - // So we expect "K8sIOClientGoModVersion" to return error - _, err := K8sIOClientGoModVersion() - require.ErrorContains(t, err, "k8s.io/client-go not found in build info") + // Whether module info is embedded in a test binary depends on the Go + // release: builds before Go 1.27 omit it, so the lookup fails. Accept + // either outcome, but require that a successful lookup returns a version. + v, err := K8sIOClientGoModVersion() + if err != nil { + require.ErrorContains(t, err, "k8s.io/client-go not found in build info") + return + } + require.True(t, strings.HasPrefix(v, "v"), "expected a semver-like version, got %q", v) } diff --git a/pkg/chart/v2/loader/load.go b/pkg/chart/v2/loader/load.go index d7b125b9b..feaa28f12 100644 --- a/pkg/chart/v2/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -153,7 +153,7 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { } fname := strings.TrimPrefix(f.Name, "charts/") - cname := strings.SplitN(fname, "/", 2)[0] + cname, _, _ := strings.Cut(fname, "/") subcharts[cname] = append(subcharts[cname], &archive.BufferedFile{Name: fname, ModTime: f.ModTime, Data: f.Data}) default: c.Files = append(c.Files, &common.File{Name: f.Name, ModTime: f.ModTime, Data: f.Data}) diff --git a/pkg/chart/v2/util/dependencies.go b/pkg/chart/v2/util/dependencies.go index 019cd1cda..ae5b224ce 100644 --- a/pkg/chart/v2/util/dependencies.go +++ b/pkg/chart/v2/util/dependencies.go @@ -46,7 +46,6 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values, if c != "" { // retrieve value vv, err := cvals.PathValue(cpath + c) - var errNoValue common.ErrNoValue if err == nil { // if not bool, warn if bv, ok := vv.(bool); ok { @@ -54,7 +53,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values, break } slog.Warn("returned non-bool value", "path", c, "chart", r.Name) - } else if !errors.As(err, &errNoValue) { + } else if _, ok := errors.AsType[common.ErrNoValue](err); !ok { // this is a real error slog.Warn("the method PathValue returned error", slog.Any("error", err)) } diff --git a/pkg/chart/v2/util/doc.go b/pkg/chart/v2/util/doc.go index ed741a83d..70dc7199f 100644 --- a/pkg/chart/v2/util/doc.go +++ b/pkg/chart/v2/util/doc.go @@ -42,4 +42,4 @@ into a Chart. When creating charts in memory, use the 'helm.sh/helm/pkg/chart' package directly. */ -package util // import "helm.sh/helm/v4/pkg/chart/v2/util" +package util diff --git a/pkg/chart/v2/util/save_test.go b/pkg/chart/v2/util/save_test.go index 1795226bc..6599addb2 100644 --- a/pkg/chart/v2/util/save_test.go +++ b/pkg/chart/v2/util/save_test.go @@ -298,13 +298,10 @@ func TestSaveDir(t *testing.T) { } func TestRepeatableSave(t *testing.T) { - tmp := t.TempDir() - defer os.RemoveAll(tmp) modTime := time.Date(2021, 9, 1, 20, 34, 58, 651387237, time.UTC) tests := []struct { name string chart *chart.Chart - want string }{ { name: "Package 1 file", @@ -325,7 +322,6 @@ func TestRepeatableSave(t *testing.T) { Schema: []byte("{\n \"title\": \"Values\"\n}"), SchemaModTime: modTime, }, - want: "63358874b93ea095c857cd66bcf5d0a4464840cf84a07547db744d81d6c5af59", }, { name: "Package 2 files", @@ -347,20 +343,26 @@ func TestRepeatableSave(t *testing.T) { Schema: []byte("{\n \"title\": \"Values\"\n}"), SchemaModTime: modTime, }, - want: "c2a43990053da788ad4e260d3b00d52a0b103ccc67ab9f48278a7b6dcfb2a4bd", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - // create package - dest := path.Join(tmp, "newdir") - where, err := Save(test.chart, dest) + // Package the same chart twice, into separate directories so the + // second Save does not simply overwrite the first. + first, err := Save(test.chart, t.TempDir()) + require.NoError(t, err, "Failed to save") + second, err := Save(test.chart, t.TempDir()) require.NoError(t, err, "Failed to save") - // get shasum for package - result, err := sha256Sum(where) + + firstSum, err := sha256Sum(first) require.NoError(t, err, "Failed to check shasum") - // assert that the package SHA is what we wanted. - assert.Equal(t, test.want, result, "FormatName() result = %v, want %v", result, test.want) + secondSum, err := sha256Sum(second) + require.NoError(t, err, "Failed to check shasum") + + // Packaging a chart must be reproducible. The digest itself is not + // pinned here because the bytes depend on the compression + // implementation of the Go release Helm is built with. + assert.Equal(t, firstSum, secondSum, "Save() is not repeatable") }) } } diff --git a/pkg/cmd/dependency_build.go b/pkg/cmd/dependency_build.go index 2fb997e4c..89bcd8a1d 100644 --- a/pkg/cmd/dependency_build.go +++ b/pkg/cmd/dependency_build.go @@ -83,8 +83,7 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command { man.Verify = downloader.VerifyIfPossible } err = man.Build() - var e downloader.ErrRepoNotFound - if errors.As(err, &e) { + if e, ok := errors.AsType[downloader.ErrRepoNotFound](err); ok { return fmt.Errorf("%s. Please add the missing repos via 'helm repo add'", e.Error()) } return err diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 67b1cf47b..00bb4be9d 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -482,8 +482,7 @@ func reformatExecErrorMsg(filename string, err error) error { // If it can parse out details from that error message such as the line number, template it failed on, // and error description, then it will construct a new error that displays these details in a structured way. // If there are issues with parsing the error message, the err passed into the function should return instead. - var execError template.ExecError - if !errors.As(err, &execError) { + if _, ok := errors.AsType[template.ExecError](err); !ok { return err } diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index aebb6f11c..e9432b195 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -447,7 +447,7 @@ func verifyInsecureSkipVerify(t *testing.T, g *HTTPGetter, caseName string, expe require.NoError(t, err) require.NotNil(t, returnVal, "Expected non nil value for http client") - transport := (returnVal.Transport).(*http.Transport) + transport := returnVal.Transport.(*http.Transport) gotValue := false if transport.TLSClientConfig != nil { gotValue = transport.TLSClientConfig.InsecureSkipVerify @@ -465,7 +465,7 @@ func TestDefaultHTTPTransportReuse(t *testing.T) { require.NotNil(t, httpClient1, "Expected non nil value for http client") - transport1 := (httpClient1.Transport).(*http.Transport) + transport1 := httpClient1.Transport.(*http.Transport) httpClient2, err := g.httpClient(g.opts) @@ -473,7 +473,7 @@ func TestDefaultHTTPTransportReuse(t *testing.T) { require.NotNil(t, httpClient2, "Expected non nil value for http client") - transport2 := (httpClient2.Transport).(*http.Transport) + transport2 := httpClient2.Transport.(*http.Transport) require.Equal(t, transport2, transport1, "Expected default transport to be reused") } @@ -489,7 +489,7 @@ func TestHTTPTransportOption(t *testing.T) { require.NotNil(t, httpClient1, "Expected non nil value for http client") - transport1 := (httpClient1.Transport).(*http.Transport) + transport1 := httpClient1.Transport.(*http.Transport) require.Equal(t, transport, transport1, "Expected transport option to be applied") @@ -499,7 +499,7 @@ func TestHTTPTransportOption(t *testing.T) { require.NotNil(t, httpClient2, "Expected non nil value for http client") - transport2 := (httpClient2.Transport).(*http.Transport) + transport2 := httpClient2.Transport.(*http.Transport) require.Equal(t, transport2, transport1, "Expected applied transport to be reused") diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 86574a3e5..60f6b6fa1 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -79,7 +79,6 @@ func newPodWithStatus(name string, status v1.PodStatus, namespace string) v1.Pod ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: ns, - SelfLink: "/api/v1/namespaces/default/pods/" + name, }, Spec: v1.PodSpec{ Containers: []v1.Container{{ diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 4c7b9a6c8..672f5603a 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -231,7 +231,7 @@ type ( // Returns true if the host contains a path component (i.e., contains a '/'). func warnIfHostHasPath(host string) bool { if strings.Contains(host, "/") { - registryHost := strings.Split(host, "/")[0] + registryHost, _, _ := strings.Cut(host, "/") slog.Warn("registry login currently only supports registry hostname, not a repository path", "host", host, "suggested", registryHost) return true } @@ -322,6 +322,11 @@ func ensureTLSConfig(client *auth.Client, setConfig *tls.Config) (*tls.Config, e transport.TLSClientConfig = &tls.Config{} } + // Idle connections were established under the previous TLS configuration. + // Drop them so the settings being applied here take effect on the next + // request instead of being bypassed by a pooled connection. + transport.CloseIdleConnections() + return transport.TLSClientConfig, nil } diff --git a/pkg/repo/v1/repotest/server.go b/pkg/repo/v1/repotest/server.go index 06cdc9172..9eb32c47b 100644 --- a/pkg/repo/v1/repotest/server.go +++ b/pkg/repo/v1/repotest/server.go @@ -216,6 +216,19 @@ func (srv *OCIServer) RunWithReturn(t *testing.T, opts ...OCIServerOpt) *OCIServ go srv.ListenAndServe() + // NewOCIServer released the port it reserved so the registry could claim + // it, and ListenAndServe binds asynchronously. Wait for the port to accept + // connections so the login below does not race the listener coming up. + dialer := &net.Dialer{Timeout: time.Second} + require.Eventually(t, func() bool { + conn, err := dialer.DialContext(t.Context(), "tcp", srv.RegistryURL) + if err != nil { + return false + } + conn.Close() + return true + }, 30*time.Second, 20*time.Millisecond, "test registry never started listening on %s", srv.RegistryURL) + credentialsFile := filepath.Join(srv.Dir, "config.json") // init test client From 57a67b30d2102e12a0286c53c70ab84bcf506e83 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Fri, 28 Aug 2026 22:09:16 +0800 Subject: [PATCH 539/571] fix(downloader): pass getter options when fetching cached provenance Signed-off-by: cuishuang --- pkg/downloader/chart_downloader.go | 2 +- pkg/downloader/chart_downloader_test.go | 51 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index eca4e3342..712c80ad2 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -290,7 +290,7 @@ func (c *ChartDownloader) DownloadToCache(ref, version string) (string, *provena return pth, ver, err } - body, err := g.Get(u.String() + ".prov") + body, err := g.Get(u.String()+".prov", c.Options...) if err != nil { if c.Verify == VerifyAlways { return pth, ver, fmt.Errorf("failed to fetch provenance %q", u.String()+".prov") diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index 92d4f024d..15e127b8c 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -18,6 +18,8 @@ package downloader import ( "crypto/sha256" "encoding/hex" + "net/http" + "net/http/httptest" "os" "path/filepath" "testing" @@ -430,6 +432,55 @@ func TestDownloadToCache(t *testing.T) { }) } +func TestDownloadToCachePassesOptionsToProvenance(t *testing.T) { + chartData, err := os.ReadFile("testdata/signtest-0.1.0.tgz") + require.NoError(t, err) + provData, err := os.ReadFile("testdata/signtest-0.1.0.tgz.prov") + require.NoError(t, err) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + username, password, ok := r.BasicAuth() + if !ok || username != "username" || password != "password" { + w.WriteHeader(http.StatusUnauthorized) + return + } + + switch r.URL.Path { + case "/signtest-0.1.0.tgz": + _, _ = w.Write(chartData) + case "/signtest-0.1.0.tgz.prov": + _, _ = w.Write(provData) + default: + http.NotFound(w, r) + } + })) + t.Cleanup(srv.Close) + + contentCache := t.TempDir() + c := ChartDownloader{ + Out: os.Stderr, + Verify: VerifyLater, + RepositoryConfig: repoConfig, + RepositoryCache: repoCache, + Getters: getter.All(&cli.EnvSettings{ + RepositoryConfig: repoConfig, + RepositoryCache: repoCache, + ContentCache: contentCache, + }), + Options: []getter.Option{ + getter.WithBasicAuth("username", "password"), + }, + Cache: &DiskCache{Root: contentCache}, + } + + _, _, err = c.DownloadToCache(srv.URL+"/signtest-0.1.0.tgz", "") + require.NoError(t, err) + + digest := sha256.Sum256(chartData) + _, err = c.Cache.Get(digest, CacheProv) + require.NoError(t, err, "provenance file should be in cache") +} + func TestStripDigestAlgorithm(t *testing.T) { tests := map[string]struct { input string From 11e2010aebedf9f495a5c295635df096de7252f7 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Mon, 31 Aug 2026 21:43:42 +0200 Subject: [PATCH 540/571] refactor: remove per-file decompression size limit (#31748) * refactor: remove per-file decompression size limit Remove MaxDecompressedFileSize as it's no longer necessary after migrating to a maintained JSON schema library (santhosh-tekuri/jsonschema/v6). The original limit was added to protect against vulnerabilities in an unmaintained library. The total decompressed chart size limit (MaxDecompressedChartSize) remains to protect against other attack vectors. Partially resolves #30738 Related: - https://github.com/helm/helm/pull/30743 Signed-off-by: Benoit Tigeot * fix: deprecate MaxDecompressedFileSize instead of removing As Matt suggested we should keep the variable until v5 as it can be used because it is public. Related: - https://github.com/helm/helm/pull/31748#discussion_r2738518696 Signed-off-by: Benoit Tigeot * fix: enforce aggregate size budget on directory loading Directory-based chart loading (`LoadDir`) used unbounded `os.ReadFile` calls with no total size check. Archive loading already enforces `MaxDecompressedChartSize` via a remaining-byte budget but directory loading did not, leaving local charts and `file://` dependencies as an unbounded memory path. Add `ReadFileWithBudget` in the archive package and use it in both v2 and v3 directory loaders so they track the same aggregate budget. Ref: https://github.com/helm/helm/pull/31748#issuecomment-4138927643 Signed-off-by: Benoit Tigeot * fix: cap directory budget reads with LimitReader Use `os.Open` + `io.LimitReader` instead of `os.ReadFile` in `ReadFileWithBudget` so a file that grows between stat and read cannot allocate unbounded memory. Also fix `MaxDecompressedFileSize` doc comment to reflect it is unused/deprecated, add nil guard on remaining, and check `os.Stat` errors in tests. Signed-off-by: Benoit Tigeot * test: add v3 directory loader budget test Mirror the v2 `TestLoadDirExceedsBudget` test for the v3 loader to prevent budget enforcement regressions in either path. Signed-off-by: Benoit Tigeot * refactor(loader): make read budget configurable Follow recommendations from https://github.com/helm/helm/pull/31748#discussion_r3058581419 Signed-off-by: Benoit Tigeot * fix(loader): export BudgetedReader for cross-package use Signed-off-by: Benoit Tigeot * fix(loader): rename max param to avoid shadowing built-in Signed-off-by: Benoit Tigeot --------- Signed-off-by: Benoit Tigeot --- internal/chart/v3/loader/directory.go | 11 +- internal/chart/v3/loader/load_test.go | 10 ++ pkg/chart/loader/archive/archive.go | 10 +- pkg/chart/loader/archive/budget.go | 74 ++++++++++++++ pkg/chart/loader/archive/budget_test.go | 128 ++++++++++++++++++++++++ pkg/chart/v2/loader/directory.go | 11 +- pkg/chart/v2/loader/load_test.go | 10 ++ 7 files changed, 238 insertions(+), 16 deletions(-) create mode 100644 pkg/chart/loader/archive/budget.go create mode 100644 pkg/chart/loader/archive/budget_test.go diff --git a/internal/chart/v3/loader/directory.go b/internal/chart/v3/loader/directory.go index 7cf343568..535468dd1 100644 --- a/internal/chart/v3/loader/directory.go +++ b/internal/chart/v3/loader/directory.go @@ -43,6 +43,10 @@ func (l DirLoader) Load() (*chart.Chart, error) { // // This loads charts only from directories. func LoadDir(dir string) (*chart.Chart, error) { + return loadDir(dir, archive.MaxDecompressedChartSize) +} + +func loadDir(dir string, budget int64) (*chart.Chart, error) { topdir, err := filepath.Abs(dir) if err != nil { return nil, err @@ -64,6 +68,7 @@ func LoadDir(dir string) (*chart.Chart, error) { files := []*archive.BufferedFile{} topdir += string(filepath.Separator) + budgetReader := archive.NewBudgetedReader(budget) walk := func(name string, fi os.FileInfo, err error) error { n := strings.TrimPrefix(name, topdir) @@ -100,11 +105,7 @@ func LoadDir(dir string) (*chart.Chart, error) { return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name) } - if fi.Size() > archive.MaxDecompressedFileSize { - return fmt.Errorf("chart file %q is larger than the maximum file size %d", fi.Name(), archive.MaxDecompressedFileSize) - } - - data, err := os.ReadFile(name) + data, err := budgetReader.ReadFileWithBudget(name, fi.Size()) if err != nil { return fmt.Errorf("error reading %s: %w", n, err) } diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index e34364f39..2163cf4e2 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -50,6 +50,16 @@ func TestLoadDir(t *testing.T) { verifyDependenciesLock(t, c) } +func TestLoadDirExceedsBudget(t *testing.T) { + _, err := loadDir("testdata/frobnitz", 1) + if err == nil { + t.Fatal("expected error when chart directory exceeds budget") + } + if !strings.Contains(err.Error(), "chart exceeds maximum decompressed size") { + t.Fatalf("unexpected error: %v", err) + } +} + func TestLoadDirWithDevNull(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("test only works on unix systems with /dev/null present") diff --git a/pkg/chart/loader/archive/archive.go b/pkg/chart/loader/archive/archive.go index a35c0152d..633871bcc 100644 --- a/pkg/chart/loader/archive/archive.go +++ b/pkg/chart/loader/archive/archive.go @@ -37,8 +37,10 @@ import ( // The default value is 100 MiB. var MaxDecompressedChartSize int64 = 100 * 1024 * 1024 // Default 100 MiB -// MaxDecompressedFileSize is the size of the largest file that Helm will attempt to load. -// The size of the file is the decompressed version of it when it is stored in an archive. +// MaxDecompressedFileSize was the per-file size limit enforced during chart loading. +// It is no longer used internally; aggregate chart size is enforced via MaxDecompressedChartSize. +// +// Deprecated: Retained for backward compatibility with external callers. Will be removed in Helm v5. var MaxDecompressedFileSize int64 = 5 * 1024 * 1024 // Default 5 MiB var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`) @@ -128,10 +130,6 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize) } - if hd.Size > MaxDecompressedFileSize { - return nil, fmt.Errorf("decompressed chart file %q is larger than the maximum file size %d", hd.Name, MaxDecompressedFileSize) - } - limitedReader := io.LimitReader(tr, remainingSize) bytesWritten, err := io.Copy(b, limitedReader) diff --git a/pkg/chart/loader/archive/budget.go b/pkg/chart/loader/archive/budget.go new file mode 100644 index 000000000..3f9b90e4a --- /dev/null +++ b/pkg/chart/loader/archive/budget.go @@ -0,0 +1,74 @@ +/* +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 archive + +import ( + "fmt" + "io" + "math" + "os" +) + +// BudgetedReader tracks cumulative file reads against a size limit. +type BudgetedReader struct { + max int64 + remaining int64 +} + +// NewBudgetedReader creates a BudgetedReader with the given maximum total size. +// The remaining budget is initialized to the maximum. +func NewBudgetedReader(limit int64) *BudgetedReader { + return &BudgetedReader{ + max: limit, + remaining: limit, + } +} + +// ReadFileWithBudget reads a file and decrements the remaining budget by the bytes read. +// It returns an error if the total would exceed the configured maximum. +// The read is capped via io.LimitReader so a file that grows between stat +// and read cannot cause unbounded memory allocation. +func (r *BudgetedReader) ReadFileWithBudget(path string, size int64) ([]byte, error) { + if size > r.remaining { + return nil, fmt.Errorf("chart exceeds maximum decompressed size of %d bytes", r.max) + } + + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + // Read at most r.remaining+1 bytes so we can detect over-budget without + // allocating unbounded memory if the file grew since stat. + // Clamp to avoid int64 overflow when r.remaining is near math.MaxInt64. + limit := r.remaining + if limit < math.MaxInt64 { + limit++ + } + data, err := io.ReadAll(io.LimitReader(f, limit)) + if err != nil { + return nil, err + } + + if int64(len(data)) > r.remaining { + return nil, fmt.Errorf("chart exceeds maximum decompressed size of %d bytes", r.max) + } + + r.remaining -= int64(len(data)) + return data, nil +} diff --git a/pkg/chart/loader/archive/budget_test.go b/pkg/chart/loader/archive/budget_test.go new file mode 100644 index 000000000..f37f78687 --- /dev/null +++ b/pkg/chart/loader/archive/budget_test.go @@ -0,0 +1,128 @@ +/* +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 archive + +import ( + "fmt" + "os" + "path/filepath" + "testing" +) + +func TestReadFileWithBudget(t *testing.T) { + dir := t.TempDir() + + writeFile := func(t *testing.T, name string, size int) string { + t.Helper() + p := filepath.Join(dir, name) + if err := os.WriteFile(p, make([]byte, size), 0644); err != nil { + t.Fatal(err) + } + return p + } + + tcs := []struct { + name string + check func(t *testing.T) + }{ + { + name: "reads file and decrements budget", + check: func(t *testing.T) { + t.Helper() + p := writeFile(t, "small.txt", 100) + fi, err := os.Stat(p) + if err != nil { + t.Fatalf("failed to stat %s: %v", p, err) + } + limit := int64(1000) + + br := NewBudgetedReader(limit) + data, err := br.ReadFileWithBudget(p, fi.Size()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(data) != 100 { + t.Fatalf("expected 100 bytes, got %d", len(data)) + } + if br.remaining != 900 { + t.Fatalf("expected remaining=900, got %d", br.remaining) + } + }, + }, + { + name: "rejects file exceeding budget", + check: func(t *testing.T) { + t.Helper() + p := writeFile(t, "big.txt", 500) + fi, err := os.Stat(p) + if err != nil { + t.Fatalf("failed to stat %s: %v", p, err) + } + limit := int64(100) + + br := NewBudgetedReader(limit) + _, err = br.ReadFileWithBudget(p, fi.Size()) + if err == nil { + t.Fatal("expected error for file exceeding budget") + } + expectedErr := fmt.Sprintf("chart exceeds maximum decompressed size of %d bytes", limit) + if err.Error() != expectedErr { + t.Fatalf("expected %q, got %q", expectedErr, err.Error()) + } + if br.remaining != 100 { + t.Fatalf("budget should not change on rejection, got %d", br.remaining) + } + }, + }, + { + name: "tracks budget across multiple reads", + check: func(t *testing.T) { + t.Helper() + remaining := int64(250) + + br := NewBudgetedReader(remaining) + for i := range 3 { + p := writeFile(t, fmt.Sprintf("f%d.txt", i), 80) + fi, err := os.Stat(p) + if err != nil { + t.Fatalf("failed to stat %s: %v", p, err) + } + if _, err := br.ReadFileWithBudget(p, fi.Size()); err != nil { + t.Fatalf("read %d: unexpected error: %v", i, err) + } + } + if br.remaining != 10 { + t.Fatalf("expected remaining=10, got %d", br.remaining) + } + + p := writeFile(t, "over.txt", 20) + fi, err := os.Stat(p) + if err != nil { + t.Fatalf("failed to stat %s: %v", p, err) + } + _, err = br.ReadFileWithBudget(p, fi.Size()) + if err == nil { + t.Fatal("expected error when cumulative reads exceed budget") + } + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, tc.check) + } +} diff --git a/pkg/chart/v2/loader/directory.go b/pkg/chart/v2/loader/directory.go index a609ecbbf..bfc649f2a 100644 --- a/pkg/chart/v2/loader/directory.go +++ b/pkg/chart/v2/loader/directory.go @@ -43,6 +43,10 @@ func (l DirLoader) Load() (*chart.Chart, error) { // // This loads charts only from directories. func LoadDir(dir string) (*chart.Chart, error) { + return loadDir(dir, archive.MaxDecompressedChartSize) +} + +func loadDir(dir string, budget int64) (*chart.Chart, error) { topdir, err := filepath.Abs(dir) if err != nil { return nil, err @@ -64,6 +68,7 @@ func LoadDir(dir string) (*chart.Chart, error) { files := []*archive.BufferedFile{} topdir += string(filepath.Separator) + budgetReader := archive.NewBudgetedReader(budget) walk := func(name string, fi os.FileInfo, err error) error { n := strings.TrimPrefix(name, topdir) @@ -100,11 +105,7 @@ func LoadDir(dir string) (*chart.Chart, error) { return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name) } - if fi.Size() > archive.MaxDecompressedFileSize { - return fmt.Errorf("chart file %q is larger than the maximum file size %d", fi.Name(), archive.MaxDecompressedFileSize) - } - - data, err := os.ReadFile(name) + data, err := budgetReader.ReadFileWithBudget(name, fi.Size()) if err != nil { return fmt.Errorf("error reading %s: %w", n, err) } diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index ae1da1130..5af88b341 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -50,6 +50,16 @@ func TestLoadDir(t *testing.T) { verifyDependenciesLock(t, c) } +func TestLoadDirExceedsBudget(t *testing.T) { + _, err := loadDir("testdata/frobnitz", 1) + if err == nil { + t.Fatal("expected error when chart directory exceeds budget") + } + if !strings.Contains(err.Error(), "chart exceeds maximum decompressed size") { + t.Fatalf("unexpected error: %v", err) + } +} + func TestLoadDirWithDevNull(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("test only works on unix systems with /dev/null present") From 9a3c040369361678aef1ab251e83a38b455f5159 Mon Sep 17 00:00:00 2001 From: Aaron Mark <64331623+amarkdotdev@users.noreply.github.com> Date: Mon, 31 Aug 2026 22:50:46 +0300 Subject: [PATCH 541/571] fix(template): regression - route registry messages to stderr in template and show (#32217) * fix: route registry messages to stderr in template and show When pulling an OCI chart, the registry client prints "Pulled: ..." and "Digest: ..." status lines (and deprecation/underscore warnings) to its configured output writer. Since v4.2.1 (introduced by #32056), these messages leaked into the stdout output of helm template and helm show, breaking downstream consumers such as cdk8s and other YAML parsers. Fix by passing the command's stderr to the registry client in the template and show commands instead of stdout. This keeps stdout clean for machine-readable YAML while still surfacing registry warnings and status messages on stderr for troubleshooting, rather than discarding them. The pull/push commands continue to print these messages on their normal output writer. The show command's addRegistryClient writer parameter is renamed to registryOut and wired through to the registry client, so it is no longer a no-op. Fixes #32215 Signed-off-by: amarkdotdev * test(cmd): cover registry client stderr routing via OCI pull Exercise helm template and helm show against an in-process OCI registry (repotest.NewOCIServer) and assert Pulled:/Digest: status lines appear on stderr only, keeping stdout free of registry noise. Signed-off-by: amarkdotdev * test(cmd): drop weak addRegistryClient unit test OCI pull coverage already asserts Pulled/Digest land on stderr, not stdout. Signed-off-by: amarkdotdev --------- Signed-off-by: amarkdotdev Co-authored-by: amarkdotdev --- pkg/cmd/registry_output_test.go | 109 ++++++++++++++++++++++++++++++++ pkg/cmd/show.go | 24 +++---- pkg/cmd/template.go | 2 +- 3 files changed, 122 insertions(+), 13 deletions(-) create mode 100644 pkg/cmd/registry_output_test.go diff --git a/pkg/cmd/registry_output_test.go b/pkg/cmd/registry_output_test.go new file mode 100644 index 000000000..77682a4c4 --- /dev/null +++ b/pkg/cmd/registry_output_test.go @@ -0,0 +1,109 @@ +/* +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 cmd + +import ( + "bytes" + "fmt" + "io" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "helm.sh/helm/v4/pkg/action" + "helm.sh/helm/v4/pkg/chart/common" + kubefake "helm.sh/helm/v4/pkg/kube/fake" + "helm.sh/helm/v4/pkg/repo/v1/repotest" +) + +func TestTemplateOCIRegistryMessagesNotOnStdout(t *testing.T) { + defer resetEnv()() + + stdout, stderr := runOCIChartCommand(t, func(ref, registryConfig, contentCache string) []string { + return []string{ + "template", "release-name", ref, + "--version", "0.1.0", + "--plain-http", + "--registry-config", registryConfig, + "--content-cache", contentCache, + } + }) + + require.NotEmpty(t, stdout) + require.NotContains(t, stdout, "Pulled:") + require.NotContains(t, stdout, "Digest:") + require.Contains(t, stderr, "Pulled:") + require.Contains(t, stderr, "Digest:") +} + +func TestShowOCIRegistryMessagesNotOnStdout(t *testing.T) { + defer resetEnv()() + + stdout, stderr := runOCIChartCommand(t, func(ref, registryConfig, contentCache string) []string { + return []string{ + "show", "chart", ref, + "--version", "0.1.0", + "--plain-http", + "--registry-config", registryConfig, + "--content-cache", contentCache, + } + }) + + require.NotEmpty(t, stdout) + require.Contains(t, stdout, "name: oci-dependent-chart") + require.NotContains(t, stdout, "Pulled:") + require.NotContains(t, stdout, "Digest:") + require.Contains(t, stderr, "Pulled:") + require.Contains(t, stderr, "Digest:") +} + +func runOCIChartCommand(t *testing.T, argsFn func(ref, registryConfig, contentCache string) []string) (string, string) { + t.Helper() + + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testcharts/*.tgz*"), + ) + t.Cleanup(func() { srv.Stop() }) + + ociSrv, err := repotest.NewOCIServer(t, srv.Root()) + require.NoError(t, err) + ociSrv.Run(t) + + ref := fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart", ociSrv.RegistryURL) + registryConfig := filepath.Join(srv.Root(), "config.json") + contentCache := t.TempDir() + args := argsFn(ref, registryConfig, contentCache) + + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + actionConfig := &action.Configuration{ + Releases: storageFixture(), + KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, + Capabilities: common.DefaultCapabilities, + } + + root, err := newRootCmdWithConfig(actionConfig, stdout, args, SetupLogging) + require.NoError(t, err) + root.SetOut(stdout) + root.SetErr(stderr) + root.SetArgs(args) + + require.NoError(t, root.Execute(), "stdout:\n%s\nstderr:\n%s", stdout.String(), stderr.String()) + return stdout.String(), stderr.String() +} diff --git a/pkg/cmd/show.go b/pkg/cmd/show.go index 8252d933e..161a38723 100644 --- a/pkg/cmd/show.go +++ b/pkg/cmd/show.go @@ -82,9 +82,9 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: showAllDesc, Args: require.ExactArgs(1), ValidArgsFunction: validArgsFunc, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowAll - err := addRegistryClient(out, client) + err := addRegistryClient(cmd.ErrOrStderr(), client) if err != nil { return err } @@ -103,9 +103,9 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: showValuesDesc, Args: require.ExactArgs(1), ValidArgsFunction: validArgsFunc, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowValues - err := addRegistryClient(out, client) + err := addRegistryClient(cmd.ErrOrStderr(), client) if err != nil { return err } @@ -124,9 +124,9 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: showChartDesc, Args: require.ExactArgs(1), ValidArgsFunction: validArgsFunc, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowChart - err := addRegistryClient(out, client) + err := addRegistryClient(cmd.ErrOrStderr(), client) if err != nil { return err } @@ -145,9 +145,9 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: readmeChartDesc, Args: require.ExactArgs(1), ValidArgsFunction: validArgsFunc, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowReadme - err := addRegistryClient(out, client) + err := addRegistryClient(cmd.ErrOrStderr(), client) if err != nil { return err } @@ -166,9 +166,9 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: showCRDsDesc, Args: require.ExactArgs(1), ValidArgsFunction: validArgsFunc, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { client.OutputFormat = action.ShowCRDs - err := addRegistryClient(out, client) + err := addRegistryClient(cmd.ErrOrStderr(), client) if err != nil { return err } @@ -224,8 +224,8 @@ func runShow(args []string, client *action.Show) (string, error) { return client.Run(cp) } -func addRegistryClient(out io.Writer, client *action.Show) error { - registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, +func addRegistryClient(registryOut io.Writer, client *action.Show) error { + registryClient, err := newRegistryClient(registryOut, client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index 29839b871..bb0f9036a 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -85,7 +85,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.KubeVersion = parsedKubeVersion } - registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(cmd.ErrOrStderr(), client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) From 4dfbaa40c6cd67e1df345889113a7879ec66136c Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Mon, 31 Aug 2026 12:58:34 -0700 Subject: [PATCH 542/571] Remove deprecated `internal/chart/v3/` code (#32365) Signed-off-by: George Jenkins --- internal/chart/v3/util/create.go | 11 +- internal/chart/v3/util/create_test.go | 10 +- internal/chart/v3/util/validate_name.go | 110 ------------------- internal/chart/v3/util/validate_name_test.go | 93 ---------------- pkg/cmd/create.go | 5 +- 5 files changed, 11 insertions(+), 218 deletions(-) delete mode 100644 internal/chart/v3/util/validate_name.go delete mode 100644 internal/chart/v3/util/validate_name_test.go diff --git a/internal/chart/v3/util/create.go b/internal/chart/v3/util/create.go index dcb5bbb39..0c143615e 100644 --- a/internal/chart/v3/util/create.go +++ b/internal/chart/v3/util/create.go @@ -18,7 +18,7 @@ package util import ( "fmt" - "io" + "log/slog" "os" "path/filepath" "regexp" @@ -642,12 +642,6 @@ spec: restartPolicy: Never ` -// Stderr is an io.Writer to which error messages can be written -// -// In Helm 4, this will be replaced. It is needed in Helm 3 to preserve API backward -// compatibility. -var Stderr io.Writer = os.Stderr - // CreateFrom creates a new chart, but scaffolds it from the src chart. func CreateFrom(chartfile *chart.Metadata, dest, src string) error { schart, err := loader.Load(src) @@ -795,8 +789,7 @@ func Create(name, dir string) (string, error) { for _, file := range files { if _, err := os.Stat(file.path); err == nil { - // There is no handle to a preferred output stream here. - fmt.Fprintf(Stderr, "WARNING: File %q already exists. Overwriting.\n", file.path) + slog.Warn("Overwriting existing file", slog.String("file", file.path)) } if err := writeFile(file.path, file.content); err != nil { return cdir, err diff --git a/internal/chart/v3/util/create_test.go b/internal/chart/v3/util/create_test.go index ddbbf6216..a227e4e7c 100644 --- a/internal/chart/v3/util/create_test.go +++ b/internal/chart/v3/util/create_test.go @@ -18,6 +18,7 @@ package util import ( "bytes" + "log/slog" "os" "path/filepath" "testing" @@ -98,7 +99,11 @@ func TestCreateFrom(t *testing.T) { func TestCreate_Overwrite(t *testing.T) { tdir := t.TempDir() - var errlog bytes.Buffer + prev := slog.Default() + t.Cleanup(func() { slog.SetDefault(prev) }) + + var logBuf bytes.Buffer + slog.SetDefault(slog.New(slog.NewJSONHandler(&logBuf, nil))) _, err := Create("foo", tdir) require.NoError(t, err) @@ -109,7 +114,6 @@ func TestCreate_Overwrite(t *testing.T) { writeFile(tplname, []byte("FOO")) // Now re-run the create - Stderr = &errlog _, err = Create("foo", tdir) require.NoError(t, err) @@ -118,7 +122,7 @@ func TestCreate_Overwrite(t *testing.T) { require.NotEqual(t, "FOO", string(data), "File that should have been modified was not.") - assert.NotEqual(t, 0, errlog.Len(), "Expected warnings about overwriting files.") + assert.NotEqual(t, 0, logBuf.Len(), "Expected warnings about overwriting files.") } func TestValidateChartName(t *testing.T) { diff --git a/internal/chart/v3/util/validate_name.go b/internal/chart/v3/util/validate_name.go deleted file mode 100644 index 7d85a5b88..000000000 --- a/internal/chart/v3/util/validate_name.go +++ /dev/null @@ -1,110 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package util - -import ( - "errors" - "fmt" - "regexp" -) - -// validName is a regular expression for resource names. -// -// According to the Kubernetes help text, the regular expression it uses is: -// -// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* -// -// This follows the above regular expression (but requires a full string match, not partial). -// -// The Kubernetes documentation is here, though it is not entirely correct: -// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -var validName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) - -var ( - // errMissingName indicates that a release (name) was not provided. - errMissingName = errors.New("no name provided") - - // errInvalidName indicates that an invalid release name was provided - errInvalidName = fmt.Errorf( - "invalid release name, must match regex %s and the length must not be longer than 53", - validName.String()) - - // errInvalidKubernetesName indicates that the name does not meet the Kubernetes - // restrictions on metadata names. - errInvalidKubernetesName = fmt.Errorf( - "invalid metadata name, must match regex %s and the length must not be longer than 253", - validName.String()) -) - -const ( - // According to the Kubernetes docs (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names) - // some resource names have a max length of 63 characters while others have a max - // length of 253 characters. As we cannot be sure the resources used in a chart, we - // therefore need to limit it to 63 chars and reserve 10 chars for additional part to name - // of the resource. The reason is that chart maintainers can use release name as part of - // the resource name (and some additional chars). - maxReleaseNameLen = 53 - // maxMetadataNameLen is the maximum length Kubernetes allows for any name. - maxMetadataNameLen = 253 -) - -// ValidateReleaseName performs checks for an entry for a Helm release name -// -// For Helm to allow a name, it must be below a certain character count (53) and also match -// a regular expression. -// -// According to the Kubernetes help text, the regular expression it uses is: -// -// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* -// -// This follows the above regular expression (but requires a full string match, not partial). -// -// The Kubernetes documentation is here, though it is not entirely correct: -// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -func ValidateReleaseName(name string) error { - // This case is preserved for backwards compatibility - if name == "" { - return errMissingName - } - if len(name) > maxReleaseNameLen || !validName.MatchString(name) { - return errInvalidName - } - return nil -} - -// ValidateMetadataName validates the name field of a Kubernetes metadata object. -// -// Empty strings, strings longer than 253 chars, or strings that don't match the regexp -// will fail. -// -// According to the Kubernetes help text, the regular expression it uses is: -// -// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* -// -// This follows the above regular expression (but requires a full string match, not partial). -// -// The Kubernetes documentation is here, though it is not entirely correct: -// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -// -// Deprecated: remove in Helm 4. Name validation now uses rules defined in -// pkg/lint/rules.validateMetadataNameFunc() -func ValidateMetadataName(name string) error { - if name == "" || len(name) > maxMetadataNameLen || !validName.MatchString(name) { - return errInvalidKubernetesName - } - return nil -} diff --git a/internal/chart/v3/util/validate_name_test.go b/internal/chart/v3/util/validate_name_test.go deleted file mode 100644 index 3ea768db2..000000000 --- a/internal/chart/v3/util/validate_name_test.go +++ /dev/null @@ -1,93 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package util - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -// TestValidateReleaseName is a regression test for ValidateName -// -// Kubernetes has strict naming conventions for resource names. This test represents -// those conventions. -// -// See https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -// -// NOTE: At the time of this writing, the docs above say that names cannot begin with -// digits. However, `kubectl`'s regular expression explicit allows this, and -// Kubernetes (at least as of 1.18) also accepts resources whose names begin with digits. -func TestValidateReleaseName(t *testing.T) { - names := map[string]bool{ - "": false, - "foo": true, - "foo.bar1234baz.seventyone": true, - "FOO": false, - "123baz": true, - "foo.BAR.baz": false, - "one-two": true, - "-two": false, - "one_two": false, - "a..b": false, - "%^&#$%*@^*@&#^": false, - "example:com": false, - "example%%com": false, - "a1111111111111111111111111111111111111111111111111111111111z": false, - } - for input, expectPass := range names { - err := ValidateReleaseName(input) - if expectPass { - assert.NoErrorf(t, err, "Expected release name %q to pass validation", input) - } else { - assert.Errorf(t, err, "Expected release name %q to fail validation, but it passed", input) - } - } -} - -func TestValidateMetadataName(t *testing.T) { - names := map[string]bool{ - "": false, - "foo": true, - "foo.bar1234baz.seventyone": true, - "FOO": false, - "123baz": true, - "foo.BAR.baz": false, - "one-two": true, - "-two": false, - "one_two": false, - "a..b": false, - "%^&#$%*@^*@&#^": false, - "example:com": false, - "example%%com": false, - "a1111111111111111111111111111111111111111111111111111111111z": true, - "a1111111111111111111111111111111111111111111111111111111111z" + - "a1111111111111111111111111111111111111111111111111111111111z" + - "a1111111111111111111111111111111111111111111111111111111111z" + - "a1111111111111111111111111111111111111111111111111111111111z" + - "a1111111111111111111111111111111111111111111111111111111111z" + - "a1111111111111111111111111111111111111111111111111111111111z": false, - } - for input, expectPass := range names { - err := ValidateMetadataName(input) - if expectPass { - assert.NoError(t, err, "Expected %q to succeed", input) - } else { - assert.Error(t, err, "Expected %q to fail", input) - } - } -} diff --git a/pkg/cmd/create.go b/pkg/cmd/create.go index b38ce6a12..f7abb20dc 100644 --- a/pkg/cmd/create.go +++ b/pkg/cmd/create.go @@ -104,7 +104,7 @@ func (o *createOptions) run(out io.Writer) error { if !gates.ChartV3.IsEnabled() { return gates.ChartV3.Error() } - return o.createV3Chart(out) + return o.createV3Chart() default: return fmt.Errorf("unsupported chart API version: %s (supported: v2, v3)", o.chartAPIVersion) } @@ -136,7 +136,7 @@ func (o *createOptions) createV2Chart(out io.Writer) error { return err } -func (o *createOptions) createV3Chart(out io.Writer) error { +func (o *createOptions) createV3Chart() error { chartname := filepath.Base(o.name) cfile := &chartv3.Metadata{ Name: chartname, @@ -157,7 +157,6 @@ func (o *createOptions) createV3Chart(out io.Writer) error { return chartutilv3.CreateFrom(cfile, filepath.Dir(o.name), lstarter) } - chartutilv3.Stderr = out _, err := chartutilv3.Create(chartname, filepath.Dir(o.name)) return err } From 28e64bd2ddb5412bff6d94d62b0114bb53f40931 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 31 Aug 2026 22:06:07 +0200 Subject: [PATCH 543/571] chore: fix gofumpt extra-rules (#32486) * chore: fix gofumpt extra-rules Signed-off-by: Matthieu MOREL * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Matthieu MOREL --------- Signed-off-by: Matthieu MOREL Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .golangci.yml | 4 ++-- internal/plugin/installer/installer.go | 2 +- internal/plugin/installer/plugin_structure.go | 2 +- internal/plugin/runtime_extismv1.go | 2 +- internal/plugin/runtime_subprocess.go | 2 +- internal/plugin/signing_info.go | 2 +- internal/release/v2/util/filter.go | 2 +- internal/release/v2/util/kind_sorter.go | 2 +- internal/test/test.go | 2 +- pkg/action/install.go | 6 +++--- pkg/action/push.go | 2 +- pkg/action/registry_login.go | 2 +- pkg/action/upgrade.go | 4 ++-- pkg/action/validate_test.go | 2 +- pkg/cli/environment.go | 2 +- pkg/cli/output/output.go | 2 +- pkg/cmd/flags.go | 2 +- pkg/cmd/list.go | 2 +- pkg/cmd/pull_test.go | 2 +- pkg/cmd/registry_login.go | 2 +- pkg/cmd/repo_remove_test.go | 4 ++-- pkg/cmd/search_repo.go | 2 +- pkg/cmd/template.go | 2 +- pkg/downloader/manager.go | 17 ++++++----------- pkg/engine/engine.go | 2 +- pkg/engine/funcs.go | 4 ++-- pkg/engine/lookup_func.go | 6 +++--- pkg/kube/client.go | 4 ++-- pkg/kube/ready_test.go | 4 ++-- pkg/registry/chart.go | 2 +- pkg/registry/client.go | 2 +- pkg/registry/plugin.go | 2 +- pkg/release/v1/util/filter.go | 2 +- pkg/release/v1/util/kind_sorter.go | 2 +- pkg/repo/v1/chartrepo.go | 2 +- pkg/storage/driver/labels.go | 2 +- pkg/storage/driver/sql.go | 4 ++-- pkg/storage/driver/sql_test.go | 2 +- 38 files changed, 54 insertions(+), 59 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 9daa85ad5..20036a0b2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -13,7 +13,7 @@ formatters: gofumpt: module-path: helm.sh/helm/v4 - extra-rules: false + extra-rules: true goimports: local-prefixes: @@ -95,7 +95,6 @@ linters: - exposedSyncMutex - hugeParam - importShadow - - paramTypeCombine - ptrToRefParam - rangeValCopy - todoCommentWithoutDetail @@ -119,6 +118,7 @@ linters: enable-default-rules: true max-open-files: 2048 rules: + - name: bare-return - name: early-return arguments: - "preserve-scope" diff --git a/internal/plugin/installer/installer.go b/internal/plugin/installer/installer.go index 4728879f5..aa43992db 100644 --- a/internal/plugin/installer/installer.go +++ b/internal/plugin/installer/installer.go @@ -153,7 +153,7 @@ func NewForSource(source, version string) (installer Installer, err error) { return installer, fmt.Errorf("cannot get information about plugin source %q (if it's a local directory, does it exist?), last error was: %w", source, err) } - return + return installer, err } // FindSource determines the correct Installer for the given source. diff --git a/internal/plugin/installer/plugin_structure.go b/internal/plugin/installer/plugin_structure.go index 10647141e..ca418ac30 100644 --- a/internal/plugin/installer/plugin_structure.go +++ b/internal/plugin/installer/plugin_structure.go @@ -53,7 +53,7 @@ func detectPluginRoot(extractDir string) (string, error) { // validatePluginName checks if the plugin directory name matches the plugin name // from plugin.yaml when the plugin is in a subdirectory. -func validatePluginName(pluginRoot string, expectedName string) error { +func validatePluginName(pluginRoot, expectedName string) error { // Only validate if plugin is in a subdirectory dirName := filepath.Base(pluginRoot) if dirName == expectedName { diff --git a/internal/plugin/runtime_extismv1.go b/internal/plugin/runtime_extismv1.go index ffa108a08..678fbb564 100644 --- a/internal/plugin/runtime_extismv1.go +++ b/internal/plugin/runtime_extismv1.go @@ -212,7 +212,7 @@ func (p *ExtismV1PluginRuntime) Invoke(ctx context.Context, input *Input) (*Outp return output, nil } -func buildManifest(pluginDir string, tmpDir string, rc *RuntimeConfigExtismV1) (extism.Manifest, error) { +func buildManifest(pluginDir, tmpDir string, rc *RuntimeConfigExtismV1) (extism.Manifest, error) { wasmFile := filepath.Join(pluginDir, ExtismV1WasmBinaryFilename) allowedHosts := rc.AllowedHosts diff --git a/internal/plugin/runtime_subprocess.go b/internal/plugin/runtime_subprocess.go index 9ea69e116..e67d1da9b 100644 --- a/internal/plugin/runtime_subprocess.go +++ b/internal/plugin/runtime_subprocess.go @@ -112,7 +112,7 @@ func (r *SubprocessPluginRuntime) Invoke(ctx context.Context, input *Input) (*Ou // InvokeWithEnv executes a plugin command with custom environment and I/O streams // This method allows execution with different command/args than the plugin's default -func (r *SubprocessPluginRuntime) InvokeWithEnv(main string, argv []string, env []string, stdin io.Reader, stdout, stderr io.Writer) error { +func (r *SubprocessPluginRuntime) InvokeWithEnv(main string, argv, env []string, stdin io.Reader, stdout, stderr io.Writer) error { mainCmdExp := os.ExpandEnv(main) cmd := exec.CommandContext(context.Background(), mainCmdExp, argv...) cmd.Env = slices.Clone(os.Environ()) diff --git a/internal/plugin/signing_info.go b/internal/plugin/signing_info.go index ff27cc02d..8077eb09a 100644 --- a/internal/plugin/signing_info.go +++ b/internal/plugin/signing_info.go @@ -111,7 +111,7 @@ func GetPluginSigningInfo(metadata Metadata) (*SigningInfo, error) { }, nil } -func validateProvenanceHash(blockContent string, tarballPath string) bool { +func validateProvenanceHash(blockContent, tarballPath string) bool { // Parse provenance to get the expected hash _, sums, err := parsePluginMessageBlock([]byte(blockContent)) if err != nil { diff --git a/internal/release/v2/util/filter.go b/internal/release/v2/util/filter.go index 0daf06014..6ad5cce9f 100644 --- a/internal/release/v2/util/filter.go +++ b/internal/release/v2/util/filter.go @@ -41,7 +41,7 @@ func (fn FilterFunc) Filter(rels []*v2.Release) (rets []*v2.Release) { rets = append(rets, rel) } } - return + return rets } // Any returns a FilterFunc that filters a list of releases diff --git a/internal/release/v2/util/kind_sorter.go b/internal/release/v2/util/kind_sorter.go index 950e836fa..4ad1f7fb3 100644 --- a/internal/release/v2/util/kind_sorter.go +++ b/internal/release/v2/util/kind_sorter.go @@ -137,7 +137,7 @@ func sortHooksByKind(hooks []*release.Hook, ordering KindSortOrder) []*release.H return h } -func lessByKind(_ any, _ any, kindA string, kindB string, o KindSortOrder) bool { +func lessByKind(_, _ any, kindA, kindB string, o KindSortOrder) bool { ordering := make(map[string]int, len(o)) for v, k := range o { ordering[k] = v diff --git a/internal/test/test.go b/internal/test/test.go index ba4ea41e7..ceb91ba3a 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -49,7 +49,7 @@ func AssertGoldenString(t TestingT, actual, filename string) { } // AssertGoldenFile asserts that the content of the actual file matches the contents of the expected file -func AssertGoldenFile(t TestingT, actualFileName string, expectedFilename string) { +func AssertGoldenFile(t TestingT, actualFileName, expectedFilename string) { t.Helper() actual, err := os.ReadFile(actualFileName) diff --git a/pkg/action/install.go b/pkg/action/install.go index 605c423bc..6fc919366 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -476,7 +476,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st return rel, err } -func (i *Install) performInstallCtx(ctx context.Context, rel *release.Release, toBeAdopted kube.ResourceList, resources kube.ResourceList) (*release.Release, error) { +func (i *Install) performInstallCtx(ctx context.Context, rel *release.Release, toBeAdopted, resources kube.ResourceList) (*release.Release, error) { type Msg struct { r *release.Release e error @@ -503,7 +503,7 @@ func (i *Install) getGoroutineCount() int32 { return i.goroutineCount.Load() } -func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.ResourceList, resources kube.ResourceList) (*release.Release, error) { +func (i *Install) performInstall(rel *release.Release, toBeAdopted, resources kube.ResourceList) (*release.Release, error) { var err error // pre-install hooks if !i.DisableHooks { @@ -717,7 +717,7 @@ func (i *Install) replaceRelease(rel *release.Release) error { } // write the to /. controls if the file is created or content will be appended -func writeToFile(outputDir string, name string, data string, appendData bool) error { +func writeToFile(outputDir, name, data string, appendData bool) error { outfileName := outputDir + string(filepath.Separator) + name err := ensureDirectoryForFile(outfileName) diff --git a/pkg/action/push.go b/pkg/action/push.go index 0c7148f65..805fdb2dc 100644 --- a/pkg/action/push.go +++ b/pkg/action/push.go @@ -90,7 +90,7 @@ func NewPushWithOpts(opts ...PushOpt) *Push { } // Run executes 'helm push' against the given chart archive. -func (p *Push) Run(chartRef string, remote string) (string, error) { +func (p *Push) Run(chartRef, remote string) (string, error) { var out strings.Builder c := uploader.ChartUploader{ diff --git a/pkg/action/registry_login.go b/pkg/action/registry_login.go index fd9d4bfc6..23bbfd02f 100644 --- a/pkg/action/registry_login.go +++ b/pkg/action/registry_login.go @@ -82,7 +82,7 @@ func NewRegistryLogin(cfg *Configuration) *RegistryLogin { } // Run executes the registry login operation -func (a *RegistryLogin) Run(_ io.Writer, hostname string, username string, password string, opts ...RegistryLoginOpt) error { +func (a *RegistryLogin) Run(_ io.Writer, hostname, username, password string, opts ...RegistryLoginOpt) error { for _, opt := range opts { if err := opt(a); err != nil { return err diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 7f66ceefb..85904124b 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -453,7 +453,7 @@ func isReleaseApplyMethodClientSideApply(applyMethod string) bool { return applyMethod == "" || applyMethod == string(release.ApplyMethodClientSideApply) } -func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *release.Release, current kube.ResourceList, target kube.ResourceList, originalRelease *release.Release, serverSideApply bool) { +func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *release.Release, current, target kube.ResourceList, originalRelease *release.Release, serverSideApply bool) { // pre-upgrade hooks if !u.DisableHooks { @@ -664,7 +664,7 @@ func mergeCustomLabels(current, desired map[string]string) map[string]string { return labels } -func getUpgradeServerSideValue(serverSideOption string, releaseApplyMethod string) (bool, error) { +func getUpgradeServerSideValue(serverSideOption, releaseApplyMethod string) (bool, error) { switch serverSideOption { case "auto": return releaseApplyMethod == "ssa", nil diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go index 8bd9f1916..d33ee683d 100644 --- a/pkg/action/validate_test.go +++ b/pkg/action/validate_test.go @@ -75,7 +75,7 @@ func newMissingDeployment(name, namespace string) *resource.Info { return info } -func newDeploymentWithOwner(name, namespace string, labels map[string]string, annotations map[string]string) *resource.Info { +func newDeploymentWithOwner(name, namespace string, labels, annotations map[string]string) *resource.Info { obj := &appsv1.Deployment{ ObjectMeta: v1.ObjectMeta{ Name: name, diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 45d773eb4..b0463d01f 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -219,7 +219,7 @@ func envCSV(name string) (ls []string) { if trimmed != "" { ls = strings.Split(trimmed, ",") } - return + return ls } func envColorMode() string { diff --git a/pkg/cli/output/output.go b/pkg/cli/output/output.go index a9bd846fe..bc4635647 100644 --- a/pkg/cli/output/output.go +++ b/pkg/cli/output/output.go @@ -85,7 +85,7 @@ func ParseFormat(s string) (out Format, err error) { default: out, err = "", ErrInvalidFormatType } - return + return out, err } // Writer is an interface that any type can implement to write supported formats diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index 45da90d0e..61db07883 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -268,7 +268,7 @@ func (p *postRendererArgsSlice) GetSlice() []string { return p.options.args } -func compVersionFlag(chartRef string, _ string) ([]string, cobra.ShellCompDirective) { +func compVersionFlag(chartRef, _ string) ([]string, cobra.ShellCompDirective) { chartInfo := strings.Split(chartRef, "/") if len(chartInfo) != 2 { return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/pkg/cmd/list.go b/pkg/cmd/list.go index 54becddca..00465d232 100644 --- a/pkg/cmd/list.go +++ b/pkg/cmd/list.go @@ -155,7 +155,7 @@ type releaseListWriter struct { noColor bool } -func newReleaseListWriter(releases []*release.Release, timeFormat string, noHeaders bool, noColor bool) *releaseListWriter { +func newReleaseListWriter(releases []*release.Release, timeFormat string, noHeaders, noColor bool) *releaseListWriter { // Initialize the array so no results returns an empty array instead of null elements := make([]releaseElement, 0, len(releases)) for _, r := range releases { diff --git a/pkg/cmd/pull_test.go b/pkg/cmd/pull_test.go index a62d6b1b4..aadb63db6 100644 --- a/pkg/cmd/pull_test.go +++ b/pkg/cmd/pull_test.go @@ -268,7 +268,7 @@ func runPullTests(t *testing.T, tests []struct { wantErrorMsg string expectFile string expectDir bool -}, outdir string, additionalFlags string, +}, outdir, additionalFlags string, ) { t.Helper() for _, tt := range tests { diff --git a/pkg/cmd/registry_login.go b/pkg/cmd/registry_login.go index bffc56445..605717e22 100644 --- a/pkg/cmd/registry_login.go +++ b/pkg/cmd/registry_login.go @@ -91,7 +91,7 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman } // Adapted from https://github.com/oras-project/oras -func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStdinOpt bool) (string, string, error) { +func getUsernamePassword(usernameOpt, passwordOpt string, passwordFromStdinOpt bool) (string, string, error) { var err error username := usernameOpt password := passwordOpt diff --git a/pkg/cmd/repo_remove_test.go b/pkg/cmd/repo_remove_test.go index f58bc91d9..2672713a2 100644 --- a/pkg/cmd/repo_remove_test.go +++ b/pkg/cmd/repo_remove_test.go @@ -119,7 +119,7 @@ func TestRepoRemove(t *testing.T) { } } -func createCacheFiles(rootDir string, repoName string) (cacheIndexFile string, cacheChartsFile string) { +func createCacheFiles(rootDir, repoName string) (cacheIndexFile, cacheChartsFile string) { cacheIndexFile = filepath.Join(rootDir, helmpath.CacheIndexFile(repoName)) mf, _ := os.Create(cacheIndexFile) mf.Close() @@ -131,7 +131,7 @@ func createCacheFiles(rootDir string, repoName string) (cacheIndexFile string, c return cacheIndexFile, cacheChartsFile } -func testCacheFiles(t *testing.T, cacheIndexFile string, cacheChartsFile string, repoName string) { +func testCacheFiles(t *testing.T, cacheIndexFile, cacheChartsFile, repoName string) { t.Helper() _, err := os.Stat(cacheIndexFile) require.Errorf(t, err, "Error cache index file was not removed for repository %s", repoName) diff --git a/pkg/cmd/search_repo.go b/pkg/cmd/search_repo.go index 348ca12b6..2d9d967f8 100644 --- a/pkg/cmd/search_repo.go +++ b/pkg/cmd/search_repo.go @@ -268,7 +268,7 @@ func (r *repoSearchWriter) encodeByFormat(out io.Writer, format output.Format) e } // Provides the list of charts that are part of the specified repo, and that starts with 'prefix'. -func compListChartsOfRepo(repoName string, prefix string) []string { +func compListChartsOfRepo(repoName, prefix string) []string { var charts []string path := filepath.Join(settings.RepositoryCache, helmpath.CacheChartsFile(repoName)) diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index bb0f9036a..5645ab33d 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -238,7 +238,7 @@ func isTestHook(h *release.Hook) bool { // bug introduced by #8156. As part of the todo to refactor renderResources // this duplicate code should be removed. It is added here so that the API // surface area is as minimally impacted as possible in fixing the issue. -func writeToFile(outputDir string, name string, data string, appendData bool) error { +func writeToFile(outputDir, name, data string, appendData bool) error { outfileName := outputDir + string(filepath.Separator) + name err := ensureDirectoryForFile(outfileName) diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index a1fa0ddbd..c4e12e1d2 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -733,22 +733,17 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]* var entry repo.ChartVersions entry, err = findEntryByName(name, cr) if err != nil { - // TODO: Where linting is skipped in this function we should - // refactor to remove naked returns while ensuring the same - // behavior - //nolint:nakedret - return + // TODO: Consider refactoring this function to reduce the number of returned values while preserving behavior. + return url, username, password, insecureSkipTLSVerify, passCredentialsAll, caFile, certFile, keyFile, err } var ve *repo.ChartVersion ve, err = findVersionedEntry(version, entry) if err != nil { - //nolint:nakedret - return + return url, username, password, insecureSkipTLSVerify, passCredentialsAll, caFile, certFile, keyFile, err } url, err = repo.ResolveReferenceURL(repoURL, ve.URLs[0]) if err != nil { - //nolint:nakedret - return + return url, username, password, insecureSkipTLSVerify, passCredentialsAll, caFile, certFile, keyFile, err } username = cr.Config.Username password = cr.Config.Password @@ -757,8 +752,8 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]* caFile = cr.Config.CAFile certFile = cr.Config.CertFile keyFile = cr.Config.KeyFile - //nolint:nakedret - return + + return url, username, password, insecureSkipTLSVerify, passCredentialsAll, caFile, certFile, keyFile, err } url, err = repo.FindChartInRepoURL(repoURL, name, m.Getters, repo.WithChartVersion(version), repo.WithClientTLS(certFile, keyFile, caFile)) if err == nil { diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 00bb4be9d..cc5917da2 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -413,7 +413,7 @@ func parseTemplateExecErrorString(s string) (TraceableError, bool) { // Special case: "template: no template %q associated with template %q" // Matches https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/text/template/exec.go;l=191 -func parseTemplateNoTemplateError(s string, remainder string) (TraceableError, bool) { +func parseTemplateNoTemplateError(s, remainder string) (TraceableError, bool) { if strings.HasPrefix(remainder, "no template ") { return TraceableError{message: s}, true } diff --git a/pkg/engine/funcs.go b/pkg/engine/funcs.go index 04a48cad3..e876df6c0 100644 --- a/pkg/engine/funcs.go +++ b/pkg/engine/funcs.go @@ -447,7 +447,7 @@ func durationWeeks(v any) float64 { // // v and m accept the same forms as asDuration (e.g. "2h13m", "30s"). // On error, it returns time.Duration(0). If m is invalid, it returns v. -func durationRoundTo(v any, m any) time.Duration { +func durationRoundTo(v, m any) time.Duration { d, err := asDuration(v) if err != nil { return 0 @@ -463,7 +463,7 @@ func durationRoundTo(v any, m any) time.Duration { // Returns a time.Duration. // // On error, it returns time.Duration(0). If m is invalid, it returns v. -func durationTruncateTo(v any, m any) time.Duration { +func durationTruncateTo(v, m any) time.Duration { d, err := asDuration(v) if err != nil { return 0 diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go index b0d0224be..df4925f05 100644 --- a/pkg/engine/lookup_func.go +++ b/pkg/engine/lookup_func.go @@ -30,7 +30,7 @@ import ( "k8s.io/client-go/rest" ) -type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]any, error) +type lookupFunc = func(apiversion, resource, namespace, name string) (map[string]any, error) // NewLookupFunction returns a function for looking up objects in the cluster. // @@ -55,7 +55,7 @@ func (c clientProviderFromConfig) GetClientFor(apiVersion, kind string) (dynamic } func newLookupFunction(ctx context.Context, clientProvider ClientProvider) lookupFunc { - return func(apiversion string, kind string, namespace string, name string) (map[string]any, error) { + return func(apiversion, kind, namespace, name string) (map[string]any, error) { var client dynamic.ResourceInterface c, namespaced, err := clientProvider.GetClientFor(apiversion, kind) if err != nil { @@ -105,7 +105,7 @@ func newLookupFunction(ctx context.Context, clientProvider ClientProvider) looku } // getDynamicClientOnKind returns a dynamic client on an Unstructured type. This client can be further namespaced. -func getDynamicClientOnKind(apiversion string, kind string, config *rest.Config) (dynamic.NamespaceableResourceInterface, bool, error) { +func getDynamicClientOnKind(apiversion, kind string, config *rest.Config) (dynamic.NamespaceableResourceInterface, bool, error) { gvk := schema.FromAPIVersionAndKind(apiversion, kind) apiRes, err := getAPIResourceForGVK(gvk, config) if err != nil { diff --git a/pkg/kube/client.go b/pkg/kube/client.go index ba881c560..03a2761f3 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -1237,8 +1237,8 @@ func upgradeClientSideFieldManager(info *resource.Info, dryRun bool, fieldValida return patched, err } -// Patch reource using server-side apply -func patchResourceServerSide(target *resource.Info, dryRun bool, forceConflicts bool, fieldValidationDirective FieldValidationDirective) error { +// Patch resource using server-side apply +func patchResourceServerSide(target *resource.Info, dryRun, forceConflicts bool, fieldValidationDirective FieldValidationDirective) error { helper := resource.NewHelper( target.Client, target.Mapping). diff --git a/pkg/kube/ready_test.go b/pkg/kube/ready_test.go index c40ad1d4d..f160db75a 100644 --- a/pkg/kube/ready_test.go +++ b/pkg/kube/ready_test.go @@ -1444,7 +1444,7 @@ func newReplicationController(name string, generationInSync bool) *corev1.Replic } } -func newReplicaSet(name string, replicas int, readyReplicas int, generationInSync bool) *appsv1.ReplicaSet { +func newReplicaSet(name string, replicas, readyReplicas int, generationInSync bool) *appsv1.ReplicaSet { d := newDeployment(name, replicas, 0, 0, generationInSync) return &appsv1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ @@ -1503,7 +1503,7 @@ func newPersistentVolumeClaim(name string, phase corev1.PersistentVolumeClaimPha } } -func newJob(name string, backoffLimit int, completions *int32, succeeded int, failed int) *batchv1.Job { +func newJob(name string, backoffLimit int, completions *int32, succeeded, failed int) *batchv1.Job { return &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: name, diff --git a/pkg/registry/chart.go b/pkg/registry/chart.go index b8b0d88b6..d0f138b18 100644 --- a/pkg/registry/chart.go +++ b/pkg/registry/chart.go @@ -108,7 +108,7 @@ func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[ } // addToMap takes an existing map and adds an item if the value is not empty -func addToMap(inputMap map[string]string, newKey string, newValue string) map[string]string { +func addToMap(inputMap map[string]string, newKey, newValue string) map[string]string { // Add item to map if its if strings.TrimSpace(newValue) != "" { inputMap[newKey] = newValue diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 672f5603a..786342c07 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -276,7 +276,7 @@ func (c *Client) Login(host string, options ...LoginOption) error { } // LoginOptBasicAuth returns a function that sets the username/password settings on login -func LoginOptBasicAuth(username string, password string) LoginOption { +func LoginOptBasicAuth(username, password string) LoginOption { return func(o *loginOperation) { o.client.username = username o.client.password = password diff --git a/pkg/registry/plugin.go b/pkg/registry/plugin.go index 959faaa7d..fa33de60a 100644 --- a/pkg/registry/plugin.go +++ b/pkg/registry/plugin.go @@ -49,7 +49,7 @@ type PluginPullResult struct { } // PullPlugin downloads a plugin from an OCI registry using artifact type -func (c *Client) PullPlugin(ref string, pluginName string, options ...PluginPullOption) (*PluginPullResult, error) { +func (c *Client) PullPlugin(ref, pluginName string, options ...PluginPullOption) (*PluginPullResult, error) { operation := &pluginPullOperation{ pluginName: pluginName, } diff --git a/pkg/release/v1/util/filter.go b/pkg/release/v1/util/filter.go index 88ac995d7..a746a9712 100644 --- a/pkg/release/v1/util/filter.go +++ b/pkg/release/v1/util/filter.go @@ -41,7 +41,7 @@ func (fn FilterFunc) Filter(rels []*rspb.Release) (rets []*rspb.Release) { rets = append(rets, rel) } } - return + return rets } // Any returns a FilterFunc that filters a list of releases diff --git a/pkg/release/v1/util/kind_sorter.go b/pkg/release/v1/util/kind_sorter.go index 01f1f801e..f112fb16e 100644 --- a/pkg/release/v1/util/kind_sorter.go +++ b/pkg/release/v1/util/kind_sorter.go @@ -137,7 +137,7 @@ func sortHooksByKind(hooks []*release.Hook, ordering KindSortOrder) []*release.H return h } -func lessByKind(_ any, _ any, kindA string, kindB string, o KindSortOrder) bool { +func lessByKind(_, _ any, kindA, kindB string, o KindSortOrder) bool { ordering := make(map[string]int, len(o)) for v, k := range o { ordering[k] = v diff --git a/pkg/repo/v1/chartrepo.go b/pkg/repo/v1/chartrepo.go index 42ed52f75..8945528f1 100644 --- a/pkg/repo/v1/chartrepo.go +++ b/pkg/repo/v1/chartrepo.go @@ -172,7 +172,7 @@ func WithInsecureSkipTLSVerify(insecureSkipTLSVerify bool) FindChartInRepoURLOpt // FindChartInRepoURL finds chart in chart repository pointed by repoURL // without adding repo to repositories -func FindChartInRepoURL(repoURL string, chartName string, getters getter.Providers, options ...FindChartInRepoURLOption) (string, error) { +func FindChartInRepoURL(repoURL, chartName string, getters getter.Providers, options ...FindChartInRepoURLOption) (string, error) { opts := findChartInRepoURLOptions{} for _, option := range options { option(&opts) diff --git a/pkg/storage/driver/labels.go b/pkg/storage/driver/labels.go index eb7118fe5..be8262b81 100644 --- a/pkg/storage/driver/labels.go +++ b/pkg/storage/driver/labels.go @@ -27,7 +27,7 @@ func (lbs labels) keys() (ls []string) { for key := range lbs { ls = append(ls, key) } - return + return ls } func (lbs labels) match(set labels) bool { diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go index db076da98..6602f2ee3 100644 --- a/pkg/storage/driver/sql.go +++ b/pkg/storage/driver/sql.go @@ -281,7 +281,7 @@ type SQLReleaseCustomLabelWrapper struct { } // NewSQL initializes a new sql driver. -func NewSQL(connectionString string, namespace string) (*SQL, error) { +func NewSQL(connectionString, namespace string) (*SQL, error) { db, err := sqlx.Connect(postgreSQLDialect, connectionString) if err != nil { return nil, err @@ -690,7 +690,7 @@ func (s *SQL) Delete(key string) (release.Releaser, error) { } // Get release custom labels from database -func (s *SQL) getReleaseCustomLabels(key string, _ string) (map[string]string, error) { +func (s *SQL) getReleaseCustomLabels(key, _ string) (map[string]string, error) { query, args, err := s.statementBuilder. Select(sqlCustomLabelsTableKeyColumn, sqlCustomLabelsTableValueColumn). From(sqlCustomLabelsTableName). diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go index 044e9df7b..e5fde405b 100644 --- a/pkg/storage/driver/sql_test.go +++ b/pkg/storage/driver/sql_test.go @@ -493,7 +493,7 @@ func TestSqlDelete(t *testing.T) { assert.Equalf(t, rel, deletedRelease, "Expected release {%v}, got {%v}", rel, deletedRelease) } -func mockGetReleaseCustomLabels(mock sqlmock.Sqlmock, key string, namespace string, labels map[string]string) { +func mockGetReleaseCustomLabels(mock sqlmock.Sqlmock, key, namespace string, labels map[string]string) { query := fmt.Sprintf( regexp.QuoteMeta("SELECT %s, %s FROM %s WHERE %s = $1 AND %s = $2"), sqlCustomLabelsTableKeyColumn, From 37752b70b2f9c49dc6ac2f01e2a0aca1f32f0529 Mon Sep 17 00:00:00 2001 From: Tom Wieczorek Date: Mon, 31 Aug 2026 22:09:13 +0200 Subject: [PATCH 544/571] refactor(repo): Use byte buffer to build index file (#32579) The string returned by the string builder was cast to a byte slice anyways. Remove this indirection. Also, write directly to the buffer instead of using fmt.Fprintln(...). Signed-off-by: Tom Wieczorek --- pkg/repo/v1/chartrepo.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/repo/v1/chartrepo.go b/pkg/repo/v1/chartrepo.go index 8945528f1..da42128bf 100644 --- a/pkg/repo/v1/chartrepo.go +++ b/pkg/repo/v1/chartrepo.go @@ -104,14 +104,15 @@ func (r *ChartRepository) DownloadIndexFile() (string, error) { } // Create the chart list file in the cache directory - var charts strings.Builder + var charts bytes.Buffer for name := range indexFile.Entries { - fmt.Fprintln(&charts, name) + charts.WriteString(name) + charts.WriteByte('\n') // Terminate each entry with a newline } chartsFile := filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)) os.MkdirAll(filepath.Dir(chartsFile), 0o755) - fileutil.AtomicWriteFile(chartsFile, bytes.NewReader([]byte(charts.String())), 0o644) + fileutil.AtomicWriteFile(chartsFile, &charts, 0o644) // Create the index file in the cache directory fname := filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name)) From ca6681ca55d39913b880871b6aa259efdf15e0b9 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Mon, 31 Aug 2026 13:35:39 -0700 Subject: [PATCH 545/571] chore: Fix independent-merge lint issues Signed-off-by: George Jenkins --- .golangci.yml | 3 ++- pkg/chart/loader/archive/budget_test.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 20036a0b2..ac8a81f0f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -13,7 +13,8 @@ formatters: gofumpt: module-path: helm.sh/helm/v4 - extra-rules: true + extra: + group-params: true goimports: local-prefixes: diff --git a/pkg/chart/loader/archive/budget_test.go b/pkg/chart/loader/archive/budget_test.go index f37f78687..f8fc6776f 100644 --- a/pkg/chart/loader/archive/budget_test.go +++ b/pkg/chart/loader/archive/budget_test.go @@ -29,7 +29,7 @@ func TestReadFileWithBudget(t *testing.T) { writeFile := func(t *testing.T, name string, size int) string { t.Helper() p := filepath.Join(dir, name) - if err := os.WriteFile(p, make([]byte, size), 0644); err != nil { + if err := os.WriteFile(p, make([]byte, size), 0o644); err != nil { t.Fatal(err) } return p From babae796e3000e03d465366b988477af44beba98 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Tue, 14 Jul 2026 19:20:33 -0700 Subject: [PATCH 546/571] chore: Cleanup deprecated and TODO comments Signed-off-by: George Jenkins --- internal/chart/v3/chart.go | 2 +- internal/monocular/search.go | 4 ++++ internal/plugin/installer/extractor.go | 2 +- internal/plugin/runtime_subprocess.go | 2 +- pkg/action/action.go | 4 ++-- pkg/action/dependency.go | 14 +++++++++----- pkg/chart/common/capabilities.go | 4 +++- pkg/chart/v2/chart.go | 2 +- pkg/chart/v2/loader/load.go | 7 ++++--- pkg/chart/v2/util/create.go | 2 +- pkg/chart/v2/util/save.go | 2 +- pkg/chart/v2/util/validate_name.go | 7 +++++-- pkg/cmd/install.go | 2 +- pkg/downloader/manager.go | 9 +++++---- pkg/engine/engine.go | 20 +++++++++++++++++--- pkg/kube/interface.go | 4 ++-- pkg/repo/v1/index.go | 21 +++++++++++---------- 17 files changed, 69 insertions(+), 39 deletions(-) diff --git a/internal/chart/v3/chart.go b/internal/chart/v3/chart.go index 71a90c855..e8e03b17e 100644 --- a/internal/chart/v3/chart.go +++ b/internal/chart/v3/chart.go @@ -141,7 +141,7 @@ func (ch *Chart) AppVersion() string { // CRDs returns a list of File objects in the 'crds/' directory of a Helm chart. // -// Deprecated: use CRDObjects() +// Deprecated: Use CRDObjects instead. func (ch *Chart) CRDs() []*common.File { files := []*common.File{} // Find all resources in the crds/ directory diff --git a/internal/monocular/search.go b/internal/monocular/search.go index 29cc7f1a4..d64d6397a 100644 --- a/internal/monocular/search.go +++ b/internal/monocular/search.go @@ -101,11 +101,15 @@ type ChartVersion struct { // Search performs a search against the monocular search API // // Deprecated: Use SearchWithContext instead. +// +//go:fix inline func (c *Client) Search(term string) ([]SearchResult, error) { return c.SearchWithContext(context.Background(), term) } // SearchWithContext performs a search against the monocular search API +// +// TODO Helm v5: Rename this to Search (remove the current deprecated Search method) and 'go:fix inline' SearchWithContext method to call Search. func (c *Client) SearchWithContext(ctx context.Context, term string) ([]SearchResult, error) { // Create the URL to the search endpoint // Note, this is currently an internal API for the Hub. This should be diff --git a/internal/plugin/installer/extractor.go b/internal/plugin/installer/extractor.go index 6cd45e2d8..4588976c4 100644 --- a/internal/plugin/installer/extractor.go +++ b/internal/plugin/installer/extractor.go @@ -48,7 +48,7 @@ var Extractors = map[string]Extractor{ // Convert a media type to an extractor extension. // -// This should be refactored in Helm 4, combined with the extension-based mechanism. +// TODO Helm v5: refactor this, combined with the extension-based mechanism. func mediaTypeToExtension(mt string) (string, bool) { switch strings.ToLower(mt) { case "application/gzip", "application/x-gzip", "application/x-tgz", "application/x-gtar": diff --git a/internal/plugin/runtime_subprocess.go b/internal/plugin/runtime_subprocess.go index e67d1da9b..2fcbcfb33 100644 --- a/internal/plugin/runtime_subprocess.go +++ b/internal/plugin/runtime_subprocess.go @@ -47,7 +47,7 @@ type RuntimeConfigSubprocess struct { PlatformHooks PlatformHooks `yaml:"platformHooks"` // ProtocolCommands allows the plugin to specify protocol specific commands // - // Obsolete/deprecated: This is a compatibility hangover from the old plugin downloader mechanism, which was extended + // Deprecated: This is an obsolete compatibility hangover from the old plugin downloader mechanism, which was extended // to support multiple protocols in a given plugin. The command supplied in PlatformCommand should implement protocol // specific logic by inspecting the download URL ProtocolCommands []SubprocessProtocolCommand `yaml:"protocolCommands,omitempty"` diff --git a/pkg/action/action.go b/pkg/action/action.go index bcc91816c..e93d6181f 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -508,8 +508,8 @@ func (cfg *Configuration) renderResources(ctx context.Context, ch *chart.Chart, newDir = filepath.Join(outputDir, releaseName) } // NOTE: We do not have to worry about the post-renderer because - // output dir is only used by `helm template`. In the next major - // release, we should move this logic to template only as it is not + // output dir is only used by `helm template`. + // TODO Helm v5: move this logic to template only as it is not // used by install or upgrade err = writeToFile(newDir, m.Name, m.Content, fileWritten[m.Name]) if err != nil { diff --git a/pkg/action/dependency.go b/pkg/action/dependency.go index 895c11e63..49f3b5f14 100644 --- a/pkg/action/dependency.go +++ b/pkg/action/dependency.go @@ -77,12 +77,14 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, p filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*") // If a chart is unpacked, this will check the unpacked chart's `charts/` directory for tarballs. - // Technically, this is COMPLETELY unnecessary, and should be removed in Helm 4. It is here - // to preserved backward compatibility. In Helm 2/3, there is a "difference" between + // + // Technically, this is COMPLETELY unnecessary. It is here to preserve backward + // compatibility. In Helm 2/3, there is a "difference" between // the tgz version (which outputs "ok" if it unpacks) and the loaded version (which outputs // "unpacked"). Early in Helm 2's history, this would have made a difference. But it no - // longer does. However, since this code shipped with Helm 3, the output must remain stable - // until Helm 4. + // longer does. However, since this code shipped with Helm 3, the output must remain stable. + // + // TODO Helm v5: remove this. switch archives, err := filepath.Glob(filepath.Join(chartpath, "charts", filename)); { case err != nil: return "bad pattern" @@ -156,7 +158,9 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, p // stat an archive and return a message if the stat is successful // // This is a refactor of the code originally in dependencyStatus. It is here to -// support legacy behavior, and should be removed in Helm 4. +// support legacy behavior. +// +// TODO Helm v5: remove this. func statArchiveForStatus(archive string, dep *chart.Dependency) string { if _, err := os.Stat(archive); err == nil { c, err := loader.Load(archive) diff --git a/pkg/chart/common/capabilities.go b/pkg/chart/common/capabilities.go index a9374a86f..c7217a503 100644 --- a/pkg/chart/common/capabilities.go +++ b/pkg/chart/common/capabilities.go @@ -87,7 +87,9 @@ func (kv *KubeVersion) String() string { // GitVersion returns the full Kubernetes version string. // -// Deprecated: use KubeVersion.Version. +// Deprecated: Use KubeVersion.Version. +// +//go:fix inline func (kv *KubeVersion) GitVersion() string { return kv.Version } // ParseKubeVersion parses kubernetes version from string diff --git a/pkg/chart/v2/chart.go b/pkg/chart/v2/chart.go index ccfe84f51..114667001 100644 --- a/pkg/chart/v2/chart.go +++ b/pkg/chart/v2/chart.go @@ -144,7 +144,7 @@ func (ch *Chart) AppVersion() string { // CRDs returns a list of File objects in the 'crds/' directory of a Helm chart. // -// Deprecated: use CRDObjects() +// Deprecated: Use CRDObjects instead. func (ch *Chart) CRDs() []*common.File { files := []*common.File{} // Find all resources in the crds/ directory diff --git a/pkg/chart/v2/loader/load.go b/pkg/chart/v2/loader/load.go index feaa28f12..fc57190ec 100644 --- a/pkg/chart/v2/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -113,8 +113,8 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { c.Schema = f.Data c.SchemaModTime = f.ModTime - // Deprecated: requirements.yaml is deprecated use Chart.yaml. - // We will handle it for you because we are nice people + // requirements.yaml is deprecated; dependencies are declared in Chart.yaml since + // apiVersion v2. Handled here for backwards compatibility. case f.Name == "requirements.yaml": if c.Metadata == nil { c.Metadata = new(chart.Metadata) @@ -128,7 +128,8 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { if c.Metadata.APIVersion == chart.APIVersionV1 { c.Files = append(c.Files, &common.File{Name: f.Name, ModTime: f.ModTime, Data: f.Data}) } - // Deprecated: requirements.lock is deprecated use Chart.lock. + // requirements.lock is deprecated; use Chart.lock. Handled here for backwards + // compatibility. case f.Name == "requirements.lock": c.Lock = new(chart.Lock) if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil { diff --git a/pkg/chart/v2/util/create.go b/pkg/chart/v2/util/create.go index 82e32d90d..65546e21b 100644 --- a/pkg/chart/v2/util/create.go +++ b/pkg/chart/v2/util/create.go @@ -643,7 +643,7 @@ spec: // Stderr is an io.Writer to which error messages can be written // -// In Helm 4, this will be replaced. It is needed in Helm 3 to preserve API backward +// TODO Helm v5: replace this. It is needed in Helm 3 to preserve API backward // compatibility. var Stderr io.Writer = os.Stderr diff --git a/pkg/chart/v2/util/save.go b/pkg/chart/v2/util/save.go index 53c952421..3dd570e1c 100644 --- a/pkg/chart/v2/util/save.go +++ b/pkg/chart/v2/util/save.go @@ -183,7 +183,7 @@ func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error { } // Save Chart.lock - // TODO: remove the APIVersion check when APIVersionV1 is not used anymore + // TODO Helm v5: remove the APIVersion check when APIVersionV1 is not used anymore if c.Metadata.APIVersion == chart.APIVersionV2 { if c.Lock != nil { ldata, err := yaml.Marshal(c.Lock) diff --git a/pkg/chart/v2/util/validate_name.go b/pkg/chart/v2/util/validate_name.go index 7d85a5b88..1304923dd 100644 --- a/pkg/chart/v2/util/validate_name.go +++ b/pkg/chart/v2/util/validate_name.go @@ -100,8 +100,11 @@ func ValidateReleaseName(name string) error { // The Kubernetes documentation is here, though it is not entirely correct: // https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names // -// Deprecated: remove in Helm 4. Name validation now uses rules defined in -// pkg/lint/rules.validateMetadataNameFunc() +// Deprecated: ValidateMetadataName is no longer used by Helm. Metadata name +// validation is now performed by the lint rule validateMetadataNameFunc (see +// pkg/chart/v2/lint/rules). +// +// TODO Helm v5: remove this function. func ValidateMetadataName(name string) error { if name == "" || len(name) > maxMetadataNameLen || !validName.MatchString(name) { return errInvalidKubernetesName diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index b6cee5843..182b4a7ff 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -214,7 +214,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal // For `helm template`, these notes flags are legacy, unused, and should not show in help, but // must remain accepted for backwards compatibility in Helm 4. Deprecate and hide them for now - // TODO remove these from template command in Helm 5 + // TODO Helm v5: remove these from template command if cmd.Name() == "template" { if err := cmd.Flags().MarkDeprecated("hide-notes", "this flag has no effect for 'helm template' and will be removed in Helm 5"); err != nil { log.Fatal(err) diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index c4e12e1d2..2e94d26ae 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -177,10 +177,11 @@ func (m *Manager) Update() error { // For the repositories Helm is not configured to know about, ensure Helm // has some information about them and, when possible, the index files // locally. - // TODO(mattfarina): Repositories should be explicitly added by end users - // rather than automatic. In Helm v4 require users to add repositories. They - // should have to add them in order to make sure they are aware of the - // repositories and opt-in to any locations, for security. + // + // TODO Helm v5: require users to add repositories explicitly rather than + // adding them automatically. They should have to add them in order to make + // sure they are aware of the repositories and opt-in to any locations, for + // security. repoNames, err = m.ensureMissingRepos(repoNames, req) if err != nil { return err diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index cc5917da2..9763bc005 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -79,6 +79,8 @@ func New(config *rest.Config) Engine { // bar chart during render time. // // Deprecated: Use RenderWithContext instead. +// +//go:fix inline func (e Engine) Render(chrt ci.Charter, values common.Values) (map[string]string, error) { return e.RenderWithContext(context.Background(), chrt, values) } @@ -102,6 +104,8 @@ func (e Engine) Render(chrt ci.Charter, values common.Values) (map[string]string // that section of the values will be passed into the "foo" chart. And if that // section contains a value named "bar", that value will be passed on to the // bar chart during render time. +// +// TODO Helm v5: Rename this to Render (remove the current deprecated Render method) and 'go:fix inline' RenderWithContext method to call Render. func (e Engine) RenderWithContext(ctx context.Context, chrt ci.Charter, values common.Values) (map[string]string, error) { tmap := allTemplates(chrt, values) return e.render(ctx, tmap) @@ -109,28 +113,38 @@ func (e Engine) RenderWithContext(ctx context.Context, chrt ci.Charter, values c // Render takes a chart, optional values, and value overrides, and attempts to // render the Go templates using the default options. +// +// Deprecated: Instantiate an Engine and call RenderWithContext instead. +// +// TODO Helm v5: Replace with a NewEngine function. +// +//go:fix inline func Render(chrt ci.Charter, values common.Values) (map[string]string, error) { - return new(Engine).Render(chrt, values) + return new(Engine).RenderWithContext(context.Background(), chrt, values) } // RenderWithClient takes a chart, optional values, and value overrides, and attempts to // render the Go templates using the default options. This engine is client aware and so can have template // functions that interact with the client. +// +// TODO Helm v5: Replace with a NewEngine function that accepts a rest.Config option func RenderWithClient(chrt ci.Charter, values common.Values, config *rest.Config) (map[string]string, error) { var clientProvider ClientProvider = clientProviderFromConfig{config} return Engine{ clientProvider: &clientProvider, - }.Render(chrt, values) + }.RenderWithContext(context.Background(), chrt, values) } // RenderWithClientProvider takes a chart, optional values, and value overrides, and attempts to // render the Go templates using the default options. This engine is client aware and so can have template // functions that interact with the client. // This function differs from RenderWithClient in that it lets you customize the way a dynamic client is constructed. +// +// TODO Helm v5: Replace with a NewEngine function that accepts a ClientProvider option func RenderWithClientProvider(chrt ci.Charter, values common.Values, clientProvider ClientProvider) (map[string]string, error) { return Engine{ clientProvider: &clientProvider, - }.Render(chrt, values) + }.RenderWithContext(context.Background(), chrt, values) } // renderable is an object that can be rendered. diff --git a/pkg/kube/interface.go b/pkg/kube/interface.go index 63c784751..c9a6a0184 100644 --- a/pkg/kube/interface.go +++ b/pkg/kube/interface.go @@ -73,7 +73,7 @@ type Interface interface { // by "\n---\n") // // Validates against OpenAPI schema if validate is true. - // TODO Helm 4: Integrate into Build with an argument + // TODO Helm v5: Integrate into Build with an argument BuildTable(reader io.Reader, validate bool) (ResourceList, error) } @@ -103,7 +103,7 @@ type Waiter interface { // InterfaceWaitOptions defines an interface that extends Interface with // methods that accept wait options. // -// TODO Helm 5: Remove InterfaceWaitOptions and integrate its method(s) into the Interface. +// TODO Helm v5: Remove InterfaceWaitOptions and integrate its method(s) into the Interface. type InterfaceWaitOptions interface { // GetWaiter gets the Kube.Waiter with options. GetWaiterWithOptions(ws WaitStrategy, opts ...WaitOption) (Waiter, error) diff --git a/pkg/repo/v1/index.go b/pkg/repo/v1/index.go index 1c1497c5e..99670eac8 100644 --- a/pkg/repo/v1/index.go +++ b/pkg/repo/v1/index.go @@ -151,7 +151,7 @@ func (i IndexFile) MustAdd(md *chart.Metadata, filename, baseURL, digest string) // Add adds a file to the index and logs an error. // -// Deprecated: Use index.MustAdd instead. +// Deprecated: Use IndexFile.MustAdd instead. Add logs errors; MustAdd returns them. func (i IndexFile) Add(md *chart.Metadata, filename, baseURL, digest string) { if err := i.MustAdd(md, filename, baseURL, digest); err != nil { slog.Error("skipping loading invalid entry for chart", "name", md.Name, "version", md.Version, "file", filename, "error", err) @@ -290,21 +290,22 @@ type ChartVersion struct { Removed bool `json:"removed,omitempty"` Digest string `json:"digest,omitempty"` - // ChecksumDeprecated is deprecated in Helm 3, and therefore ignored. Helm 3 replaced - // this with Digest. However, with a strict YAML parser enabled, a field must be - // present on the struct for backwards compatibility. + // Deprecated: ChecksumDeprecated is ignored (Helm 3 replaced it with Digest). It is + // retained only so a strict YAML parser accepts the "checksum" field for backwards + // compatibility; do not use it. ChecksumDeprecated string `json:"checksum,omitempty"` - // EngineDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict - // YAML parser enabled, this field must be present. + // Deprecated: EngineDeprecated is ignored (removed in Helm 3). It is retained only so a + // strict YAML parser accepts the "engine" field for backwards compatibility; do not use it. EngineDeprecated string `json:"engine,omitempty"` - // TillerVersionDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict - // YAML parser enabled, this field must be present. + // Deprecated: TillerVersionDeprecated is ignored (removed in Helm 3). It is retained only + // so a strict YAML parser accepts the "tillerVersion" field for backwards compatibility; + // do not use it. TillerVersionDeprecated string `json:"tillerVersion,omitempty"` - // URLDeprecated is deprecated in Helm 3, superseded by URLs. It is ignored. However, - // with a strict YAML parser enabled, this must be present on the struct. + // Deprecated: URLDeprecated is ignored (superseded by URLs). It is retained only so a + // strict YAML parser accepts the "url" field for backwards compatibility; do not use it. URLDeprecated string `json:"url,omitempty"` } From c0039291c1654ea3178ca7aebc449a980b70a719 Mon Sep 17 00:00:00 2001 From: Scott Rigby Date: Mon, 31 Aug 2026 19:19:24 -0300 Subject: [PATCH 547/571] bump version to 4.3 (#32605) Signed-off-by: Scott Rigby --- internal/version/version.go | 2 +- pkg/chart/common/capabilities_test.go | 2 +- pkg/cmd/testdata/output/version-short.txt | 2 +- pkg/cmd/testdata/output/version-template.txt | 2 +- pkg/cmd/testdata/output/version.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/version/version.go b/internal/version/version.go index 572938c96..c40e101de 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -34,7 +34,7 @@ var ( // // Increment major number for new feature additions and behavioral changes. // Increment minor number for bug fixes and performance enhancements. - version = "v4.2" + version = "v4.3" // metadata is extra build time data metadata = "" diff --git a/pkg/chart/common/capabilities_test.go b/pkg/chart/common/capabilities_test.go index b099e3193..18ff08fe9 100644 --- a/pkg/chart/common/capabilities_test.go +++ b/pkg/chart/common/capabilities_test.go @@ -46,7 +46,7 @@ func TestDefaultCapabilities(t *testing.T) { assert.Equalf(t, "20", kv.Minor, "Expected default KubeVersion.Minor to be 20, got %q", kv.Minor) hv := caps.HelmVersion - assert.Equalf(t, "v4.2", hv.Version, "Expected default HelmVersion to be v4.2, got %q", hv.Version) + assert.Equalf(t, "v4.3", hv.Version, "Expected default HelmVersion to be v4.3, got %q", hv.Version) } func TestParseKubeVersion(t *testing.T) { diff --git a/pkg/cmd/testdata/output/version-short.txt b/pkg/cmd/testdata/output/version-short.txt index 2fa2c5705..b570a0ac2 100644 --- a/pkg/cmd/testdata/output/version-short.txt +++ b/pkg/cmd/testdata/output/version-short.txt @@ -1 +1 @@ -v4.2 +v4.3 diff --git a/pkg/cmd/testdata/output/version-template.txt b/pkg/cmd/testdata/output/version-template.txt index 8f2491e54..06a6787a8 100644 --- a/pkg/cmd/testdata/output/version-template.txt +++ b/pkg/cmd/testdata/output/version-template.txt @@ -1 +1 @@ -Version: v4.2 \ No newline at end of file +Version: v4.3 \ No newline at end of file diff --git a/pkg/cmd/testdata/output/version.txt b/pkg/cmd/testdata/output/version.txt index 331c6d5e9..4b5678a7f 100644 --- a/pkg/cmd/testdata/output/version.txt +++ b/pkg/cmd/testdata/output/version.txt @@ -1 +1 @@ -version.BuildInfo{Version:"v4.2", GitCommit:"", GitTreeState:"", GoVersion:"", KubeClientVersion:"v1.20"} +version.BuildInfo{Version:"v4.3", GitCommit:"", GitTreeState:"", GoVersion:"", KubeClientVersion:"v1.20"} From 702c67e97dd419142534937974d1b7205ecd2124 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 06:32:38 -0600 Subject: [PATCH 548/571] chore(deps): bump github.com/gofrs/flock from 0.13.0 to 0.13.1 (#32601) Bumps [github.com/gofrs/flock](https://github.com/gofrs/flock) from 0.13.0 to 0.13.1. - [Release notes](https://github.com/gofrs/flock/releases) - [Commits](https://github.com/gofrs/flock/compare/v0.13.0...v0.13.1) --- updated-dependencies: - dependency-name: github.com/gofrs/flock dependency-version: 0.13.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bf8bb7228..3fd2aa8ce 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/fluxcd/cli-utils v1.2.2 github.com/foxcpp/go-mockdns v1.2.0 github.com/gobwas/glob v0.2.3 - github.com/gofrs/flock v0.13.0 + github.com/gofrs/flock v0.13.1 github.com/gosuri/uitable v0.0.4 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.12.3 diff --git a/go.sum b/go.sum index b9d8e51fc..7ef64118f 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= -github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= +github.com/gofrs/flock v0.13.1 h1:jjREztyBeSKBZYAC+mgc1laB+xsgy4kYMf3FbKF2UBo= +github.com/gofrs/flock v0.13.1/go.mod h1:sf4BFiHwnvgxa25DlQoDqXQnwRMEOwqxRq37P6MzzmE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= From 4ba1280837dda7e11d4d1e5019b14c4c94350b08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 06:40:54 -0600 Subject: [PATCH 549/571] chore(deps): bump github.com/gobwas/glob from 0.2.3 to 1.0.0 (#32602) Bumps [github.com/gobwas/glob](https://github.com/gobwas/glob) from 0.2.3 to 1.0.0. - [Release notes](https://github.com/gobwas/glob/releases) - [Commits](https://github.com/gobwas/glob/compare/v0.2.3...v1.0.0) --- updated-dependencies: - dependency-name: github.com/gobwas/glob dependency-version: 1.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3fd2aa8ce..9da20b09a 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/fatih/color v1.19.0 github.com/fluxcd/cli-utils v1.2.2 github.com/foxcpp/go-mockdns v1.2.0 - github.com/gobwas/glob v0.2.3 + github.com/gobwas/glob v1.0.0 github.com/gofrs/flock v0.13.1 github.com/gosuri/uitable v0.0.4 github.com/jmoiron/sqlx v1.4.0 diff --git a/go.sum b/go.sum index 7ef64118f..c4497aa58 100644 --- a/go.sum +++ b/go.sum @@ -128,8 +128,8 @@ github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqw github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gobwas/glob v1.0.0 h1:p+FKbLEIsK1yZ39/OINwFvqNb5oyPY4H8xcy6uYu8dg= +github.com/gobwas/glob v1.0.0/go.mod h1:oWCdo522i2P1n/hMXGNWs7yoV4wy/ciZuUIbvKj5rkc= github.com/gofrs/flock v0.13.1 h1:jjREztyBeSKBZYAC+mgc1laB+xsgy4kYMf3FbKF2UBo= github.com/gofrs/flock v0.13.1/go.mod h1:sf4BFiHwnvgxa25DlQoDqXQnwRMEOwqxRq37P6MzzmE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= From 67946bb22e9a2f39ee7ff70effca98630fafd9bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 06:51:47 -0600 Subject: [PATCH 550/571] chore(deps): bump the github-actions group across 1 directory with 4 updates (#32603) Bumps the github-actions group with 4 updates in the / directory: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action). Updates `github/codeql-action/init` from 4.37.8 to 4.37.9 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28...cdf488f595d80d6e07e03d4674febd5ab45fa938) Updates `github/codeql-action/autobuild` from 4.37.8 to 4.37.9 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28...cdf488f595d80d6e07e03d4674febd5ab45fa938) Updates `github/codeql-action/analyze` from 4.37.8 to 4.37.9 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28...cdf488f595d80d6e07e03d4674febd5ab45fa938) Updates `github/codeql-action/upload-sarif` from 4.37.8 to 4.37.9 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28...cdf488f595d80d6e07e03d4674febd5ab45fa938) --- updated-dependencies: - dependency-name: github/codeql-action/analyze dependency-version: 4.37.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/init dependency-version: 4.37.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.37.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d4d9f5228..37e3e2048 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # pinv4.37.8 + uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # pinv4.37.9 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # pinv4.37.8 + uses: github/codeql-action/autobuild@cdf488f595d80d6e07e03d4674febd5ab45fa938 # pinv4.37.9 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # pinv4.37.8 + uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # pinv4.37.9 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 6d5367952..469178ced 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 with: sarif_file: results.sarif From fa6ea21d4c4439f3d163a4a8fe79eb954c027c59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 21:36:27 +0000 Subject: [PATCH 551/571] chore(deps): bump github.com/fluxcd/cli-utils from 1.2.2 to 1.2.3 Bumps [github.com/fluxcd/cli-utils](https://github.com/fluxcd/cli-utils) from 1.2.2 to 1.2.3. - [Release notes](https://github.com/fluxcd/cli-utils/releases) - [Commits](https://github.com/fluxcd/cli-utils/compare/v1.2.2...v1.2.3) --- updated-dependencies: - dependency-name: github.com/fluxcd/cli-utils dependency-version: 1.2.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9da20b09a..1e17e54bd 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.19.0 - github.com/fluxcd/cli-utils v1.2.2 + github.com/fluxcd/cli-utils v1.2.3 github.com/foxcpp/go-mockdns v1.2.0 github.com/gobwas/glob v1.0.0 github.com/gofrs/flock v0.13.1 diff --git a/go.sum b/go.sum index c4497aa58..e4b0b867e 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/cli-utils v1.2.2 h1:adDOmwE+LSwTzmYUaoEFPblruOuaQEKAg1ZNTmPJObE= -github.com/fluxcd/cli-utils v1.2.2/go.mod h1:FsghNGY+3Sr70c0FOB7I5So0kzoYVdvQ8GTid3XXVWM= +github.com/fluxcd/cli-utils v1.2.3 h1:RcBLij09aROVOtFWzQzg2My6S68R/sIm4OfQLTXAIWE= +github.com/fluxcd/cli-utils v1.2.3/go.mod h1:RuUFNFaDCsyCWzX4ghxt54rjmyyhNWaUlUFVa/kQq2Q= github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= From 9c9c8e10ca395459692a986f859dc521a6897f79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 07:14:38 +0000 Subject: [PATCH 552/571] chore(deps): bump google.golang.org/grpc from 1.82.1 to 1.83.1 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.82.1 to 1.83.1. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.82.1...v1.83.1) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.83.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 9da20b09a..33eb0237a 100644 --- a/go.mod +++ b/go.mod @@ -165,9 +165,9 @@ require ( golang.org/x/sys v0.47.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.48.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect - google.golang.org/grpc v1.82.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/grpc v1.83.1 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index c4497aa58..8231b3496 100644 --- a/go.sum +++ b/go.sum @@ -468,12 +468,12 @@ golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec= -google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= -google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= +google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From b588bcd32f4cdbf5ab6a79ee2861e5b9fcddb667 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:02:00 +0000 Subject: [PATCH 553/571] chore(deps): bump the k8s-io group across 1 directory with 6 updates Bumps the k8s-io group with 3 updates in the / directory: [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver), [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime) and [k8s.io/kubectl](https://github.com/kubernetes/kubectl). Updates `k8s.io/apiextensions-apiserver` from 0.36.4 to 0.37.0 - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.36.4...v0.37.0) Updates `k8s.io/apimachinery` from 0.36.4 to 0.37.0 - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.36.4...v0.37.0) Updates `k8s.io/apiserver` from 0.36.4 to 0.37.0 - [Commits](https://github.com/kubernetes/apiserver/compare/v0.36.4...v0.37.0) Updates `k8s.io/cli-runtime` from 0.36.4 to 0.37.0 - [Commits](https://github.com/kubernetes/cli-runtime/compare/v0.36.4...v0.37.0) Updates `k8s.io/client-go` from 0.36.4 to 0.37.0 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.36.4...v0.37.0) Updates `k8s.io/kubectl` from 0.36.4 to 0.37.0 - [Commits](https://github.com/kubernetes/kubectl/compare/v0.36.4...v0.37.0) --- updated-dependencies: - dependency-name: k8s.io/apiextensions-apiserver dependency-version: 0.37.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: k8s-io - dependency-name: k8s.io/apimachinery dependency-version: 0.37.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: k8s-io - dependency-name: k8s.io/apiserver dependency-version: 0.37.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: k8s-io - dependency-name: k8s.io/cli-runtime dependency-version: 0.37.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: k8s-io - dependency-name: k8s.io/client-go dependency-version: 0.37.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: k8s-io - dependency-name: k8s.io/kubectl dependency-version: 0.37.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: k8s-io ... Signed-off-by: dependabot[bot] --- go.mod | 62 ++++++++++++++------------ go.sum | 136 +++++++++++++++++++++++++++++++++------------------------ 2 files changed, 115 insertions(+), 83 deletions(-) diff --git a/go.mod b/go.mod index 33eb0237a..8b6804195 100644 --- a/go.mod +++ b/go.mod @@ -38,15 +38,14 @@ require ( golang.org/x/crypto v0.55.0 golang.org/x/term v0.45.0 golang.org/x/text v0.41.0 - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.36.4 - k8s.io/apiextensions-apiserver v0.36.4 - k8s.io/apimachinery v0.36.4 - k8s.io/apiserver v0.36.4 - k8s.io/cli-runtime v0.36.4 - k8s.io/client-go v0.36.4 + k8s.io/api v0.37.0 + k8s.io/apiextensions-apiserver v0.37.0 + k8s.io/apimachinery v0.37.0 + k8s.io/apiserver v0.37.0 + k8s.io/cli-runtime v0.37.0 + k8s.io/client-go v0.37.0 k8s.io/klog/v2 v2.140.0 - k8s.io/kubectl v0.36.4 + k8s.io/kubectl v0.37.0 oras.land/oras-go/v2 v2.6.2 sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/kustomize/kyaml v0.21.1 @@ -77,34 +76,43 @@ require ( github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.1 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.21.1 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-openapi/jsonpointer v1.0.0 // indirect + github.com/go-openapi/jsonreference v1.0.0 // indirect + github.com/go-openapi/swag v0.27.1 // indirect + github.com/go-openapi/swag/cmdutils v0.27.1 // indirect + github.com/go-openapi/swag/conv v0.27.1 // indirect + github.com/go-openapi/swag/fileutils v0.27.1 // indirect + github.com/go-openapi/swag/jsonutils v0.27.1 // indirect + github.com/go-openapi/swag/loading v0.27.1 // indirect + github.com/go-openapi/swag/mangling v0.27.1 // indirect + github.com/go-openapi/swag/netutils v0.27.1 // indirect + github.com/go-openapi/swag/pools v0.27.1 // indirect + github.com/go-openapi/swag/stringutils v0.27.1 // indirect + github.com/go-openapi/swag/typeutils v0.27.1 // indirect + github.com/go-openapi/swag/yamlutils v0.27.1 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.18.4 // indirect + github.com/klauspost/compress v1.19.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect @@ -119,11 +127,11 @@ require ( github.com/onsi/gomega v1.42.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_golang v1.24.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/common v0.70.0 // indirect github.com/prometheus/otlptranslator v1.0.0 // indirect - github.com/prometheus/procfs v0.20.1 // indirect + github.com/prometheus/procfs v0.21.1 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect github.com/redis/go-redis/v9 v9.7.3 // indirect @@ -137,14 +145,14 @@ require ( go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect @@ -157,7 +165,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect - go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect golang.org/x/mod v0.38.0 // indirect golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect @@ -172,11 +180,11 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/component-base v0.36.4 // indirect - k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect - k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 // indirect + k8s.io/component-base v0.37.0 // indirect + k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect + k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.21.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.3 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect ) diff --git a/go.sum b/go.sum index 8231b3496..c1e5ba1bd 100644 --- a/go.sum +++ b/go.sum @@ -99,8 +99,8 @@ github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZ github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= -github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= +github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= @@ -117,12 +117,40 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= -github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= +github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= +github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= +github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= +github.com/go-openapi/swag v0.27.1 h1:VotvOLWW8q/EAxB0YdsBBGC8XYyeL1YwBj2ungAGPNg= +github.com/go-openapi/swag v0.27.1/go.mod h1:GTkJPwHfhJp6MWr4/rCh64HVI3Ofu+tcsbfjfHmTxpE= +github.com/go-openapi/swag/cmdutils v0.27.1 h1:I7sYqaWVl5mq0NEmNQkAmFDyNin9ufvMX/p2zwtQaOE= +github.com/go-openapi/swag/cmdutils v0.27.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.27.1 h1:8wi9ZG+olmY1wXphl93EWniPtbSPkXM/feH7FgjsvrU= +github.com/go-openapi/swag/conv v0.27.1/go.mod h1:QbqMivkpKhC3g1B1GGGOJ6ANewI3S62dbzYu3Duowqs= +github.com/go-openapi/swag/fileutils v0.27.1 h1:QQqBSoi5mW4XpU85nS0mLcA+zAE6vLzrb0QkmLKf9oM= +github.com/go-openapi/swag/fileutils v0.27.1/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= +github.com/go-openapi/swag/jsonutils v0.27.1 h1:SVgK3i4USzCU5mibOOS/l4ea2h9UQXy7J7RNLTjuXjU= +github.com/go-openapi/swag/jsonutils v0.27.1/go.mod h1:tdlEpZqdcQ17uj6J4YdK9vd8It5qWMwjWXOs0tjpRlk= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1 h1:mJu3COL9WEaZVp/Kf2PRMi7tPszPEJfSr/OO75ynCs8= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= +github.com/go-openapi/swag/loading v0.27.1 h1:/DxUgDXKbBX4bcn7r9uEXfJyzN5XpiJmZplzQTjrRCY= +github.com/go-openapi/swag/loading v0.27.1/go.mod h1:jvGh3iA2+zyUUycB5fgJWzeHnhrpvGnJJM0RVE9ZShE= +github.com/go-openapi/swag/mangling v0.27.1 h1:yC9D0HyUE8gbP+BfmGx9+AA89ikwZTMjESK3OnnoaqA= +github.com/go-openapi/swag/mangling v0.27.1/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= +github.com/go-openapi/swag/netutils v0.27.1 h1:mICMFoS82F5TZ4Zy3cqmcQk+BFeCp3Uyq3Np7GI0/qU= +github.com/go-openapi/swag/netutils v0.27.1/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= +github.com/go-openapi/swag/pools v0.27.1 h1:9LeadcMyb2GJCbXX5hVQDbZ2Lq9TL4dCs/nx1j5DO0E= +github.com/go-openapi/swag/pools v0.27.1/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= +github.com/go-openapi/swag/stringutils v0.27.1 h1:ZXePZ0r2p1qSjo8tD3Un4vFj8+FqlCkczxDrJIhYUp8= +github.com/go-openapi/swag/stringutils v0.27.1/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= +github.com/go-openapi/swag/typeutils v0.27.1 h1:KSTdFlfnse4r6dP9IrEnwMldjE+zs71UeEB3//PtVXc= +github.com/go-openapi/swag/typeutils v0.27.1/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= +github.com/go-openapi/swag/yamlutils v0.27.1 h1:ftxv6xvXb1E3zohUc+okZ9nSqNb9StQX/FXnKZ98sQA= +github.com/go-openapi/swag/yamlutils v0.27.1/go.mod h1:bnxFIB1qewGRiZHypXGZ3fNgf13/0HfRgnS/iZBDrOo= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= +github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= +github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -146,8 +174,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= @@ -156,8 +184,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= @@ -170,16 +198,14 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= -github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= -github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ= +github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -197,8 +223,6 @@ github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -233,8 +257,8 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= -github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/ginkgo/v2 v2.32.0 h1:Hw7s2pVrQo/8Yz5N77qdnpHaoc+c6cC9WIV1Jce+J6E= +github.com/onsi/ginkgo/v2 v2.32.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I= github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -252,23 +276,23 @@ github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjz github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI= +github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= -github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/common v0.70.0 h1:bcpru3tWPVnxGnETLgOV5jbp/JRXgYEyv65CuBLAMMI= +github.com/prometheus/common v0.70.0/go.mod h1:S/SFasQmgGiYH6C81LKCtYa8QACgthGg5zxL2udV7SY= github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= -github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= +github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= +github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= @@ -326,8 +350,8 @@ go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXv go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4= go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8= go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY= @@ -338,10 +362,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdK go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs= @@ -376,8 +400,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= -go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= @@ -490,28 +514,28 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.36.4 h1:RxrvqCL6vgH5/+UnTeu1IIFqYmGfy0hnyrod1rn35Oo= -k8s.io/api v0.36.4/go.mod h1:S2B3orCFBDhrgyWbLeuKcT2QdHIpQesBkCYSlWtwUOw= -k8s.io/apiextensions-apiserver v0.36.4 h1:SfvCVt+4CqKWvzuVytYDT5g9hyb9MztoiYELIkPVrFc= -k8s.io/apiextensions-apiserver v0.36.4/go.mod h1:JT9V2Ju7ys1FY4zbSpmX9XOvKB3/BwsODc4hFQEa+Xo= -k8s.io/apimachinery v0.36.4 h1:PT2UzkupGuAx/+xT5XjiMJ1WGpY3fn9/hdAvjweRet4= -k8s.io/apimachinery v0.36.4/go.mod h1:p2I2dipt7JHG+quVwQ1d02d28O4GdDi77RByQ13MTpk= -k8s.io/apiserver v0.36.4 h1:AtKjaf2eUiX5G6TfF2IOlhuUuvMsHh49Ivr1+4fZ2gA= -k8s.io/apiserver v0.36.4/go.mod h1:RyiGghXP67hb0Ll+7iLJ6GGv2JpEzCn7ljbiA+L3cJ0= -k8s.io/cli-runtime v0.36.4 h1:OHvManCwP1k9GiC5tXRFxHhzZIQQFCsrHlt7OspKo3w= -k8s.io/cli-runtime v0.36.4/go.mod h1:qQSj2FJgQos6GHpS/ge7wTdQMZm9XFWlesWgV6h7qZY= -k8s.io/client-go v0.36.4 h1:MDvfDNvMSt0Br94SK8neviVlwL9qifw9B26hJCpD1K0= -k8s.io/client-go v0.36.4/go.mod h1:pNK4WKELbwlEDvtbE8l22lEZL5THYF61H5EealokZmA= -k8s.io/component-base v0.36.4 h1:tz75yC2xgq3kd7vPdBtR8do5iMx0OHf6Zd1kuaxDB84= -k8s.io/component-base v0.36.4/go.mod h1:DCwb306U8ou89NNAp45Csuy8ok+1rp1ELDVPhzN5AWc= +k8s.io/api v0.37.0 h1:Z//Vj9N7RA/yS2sDmxyeo7h+RR4zbUrd2vrd3Z0TbB4= +k8s.io/api v0.37.0/go.mod h1:LKXgcJWMc+f4OLbP5SFR8rulEg07zZhpi/zMULiBImk= +k8s.io/apiextensions-apiserver v0.37.0 h1:zRMQ3+/LIE5oZ0tVvXwYHC+dIkSP5cjNWju7AZU1LOI= +k8s.io/apiextensions-apiserver v0.37.0/go.mod h1:HU0PfSBwchHL5iDau6jjt9zU6ryWkDDlaVUiq91NK80= +k8s.io/apimachinery v0.37.0 h1:Np2AbDtf8x6RDHiD8T9LbKJ9gaegeVNa8yNm5FuGKm0= +k8s.io/apimachinery v0.37.0/go.mod h1:RN3nhprFSCxOi5Selxd7oMTXOe/c+ZbcE7Im+TS2zkE= +k8s.io/apiserver v0.37.0 h1:TXg7OxsOWrAH8J4Zi/gBAZuMw1Dfdd+6cca2h4qjRqo= +k8s.io/apiserver v0.37.0/go.mod h1:OddHDF4gy9qyIb8o/3+qaeP6S0vEObWLgOygVqXksv0= +k8s.io/cli-runtime v0.37.0 h1:U3XakUeirBQJMz5688r04z74SIHSE7V5SIZ6Ho5JyBM= +k8s.io/cli-runtime v0.37.0/go.mod h1:qiQMFkKwFFuPH6zy953On+nc3qfpEHAIDrJmAuRz5Vg= +k8s.io/client-go v0.37.0 h1:nsN31fy8wBySuZ+QRnKmrjRSQLOG2rvoGN0tKd12zhQ= +k8s.io/client-go v0.37.0/go.mod h1:FcGqw+Ll/gNQiq+nPGY1Oyt9y7SgDh1d3MW3RFDEbn0= +k8s.io/component-base v0.37.0 h1:3SdSa4+itMdFTDFTeR8CxKGmSTSMXFlKL4ky8OqjguM= +k8s.io/component-base v0.37.0/go.mod h1:LjOebp4R9y6LODWZQv102ZQxGheLcDO2ZJLAw6bbh4I= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= -k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/kubectl v0.36.4 h1:xZd9g1bFBd7hpb1oKjK8lT9jRL18dtgr4DAPQG1Oksk= -k8s.io/kubectl v0.36.4/go.mod h1:STWlr78cdEa1hHpr55wpcboaqchvfDueKRNDa1zOd1w= -k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc= -k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= +k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= +k8s.io/kubectl v0.37.0 h1:cici6hiofx93ASldmprDmZF55SfhVt4o3HniltVLjTc= +k8s.io/kubectl v0.37.0/go.mod h1:RSeEl8e/yqDx6srG8Azr0uAtVPNIZljA0PNh9HCBcdg= +k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= +k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4= sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= @@ -524,7 +548,7 @@ sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7 sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.3 h1:u08YRbVUi59ri4YD6cg0UqNM4Dimn0sIl+wldcx5PYw= -sigs.k8s.io/structured-merge-diff/v6 v6.3.3/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q= +sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= From 357678488e5c8a427335ee9fedd05fa5f90e5fea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 05:54:45 -0600 Subject: [PATCH 554/571] chore(deps): bump sigs.k8s.io/controller-runtime from 0.24.1 to 0.25.0 (#32631) Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.24.1 to 0.25.0. - [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.24.1...v0.25.0) --- updated-dependencies: - dependency-name: sigs.k8s.io/controller-runtime dependency-version: 0.25.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a56a2df23..4dd3353bd 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( k8s.io/klog/v2 v2.140.0 k8s.io/kubectl v0.37.0 oras.land/oras-go/v2 v2.6.2 - sigs.k8s.io/controller-runtime v0.24.1 + sigs.k8s.io/controller-runtime v0.25.0 sigs.k8s.io/kustomize/kyaml v0.21.1 sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index 72bc35edf..a11ad5775 100644 --- a/go.sum +++ b/go.sum @@ -538,8 +538,8 @@ k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI0 k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4= -sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= -sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= +sigs.k8s.io/controller-runtime v0.25.0 h1:44KgRUPew331KSJpNu8zJow3iTR5W0p/SfrHdw3lV40= +sigs.k8s.io/controller-runtime v0.25.0/go.mod h1:4QqLdT6z/L6Olj8JJCtvztid4/fnIiYsfaTFScegctc= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= From 87268ee077fa400400931caf7767b536162b9c12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 06:03:42 -0600 Subject: [PATCH 555/571] chore(deps): bump golang.org/x/crypto from 0.55.0 to 0.56.0 (#32632) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.55.0 to 0.56.0. - [Commits](https://github.com/golang/crypto/compare/v0.55.0...v0.56.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.56.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4dd3353bd..56eec2aa4 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/stretchr/testify v1.12.1 github.com/tetratelabs/wazero v1.12.0 go.yaml.in/yaml/v3 v3.0.5 - golang.org/x/crypto v0.55.0 + golang.org/x/crypto v0.56.0 golang.org/x/term v0.45.0 golang.org/x/text v0.41.0 k8s.io/api v0.37.0 diff --git a/go.sum b/go.sum index a11ad5775..dbea692af 100644 --- a/go.sum +++ b/go.sum @@ -411,8 +411,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= -golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= +golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= From 614120ff25fa3a7e032d8c3825bbeec50aac4012 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 00:49:23 +0000 Subject: [PATCH 556/571] chore(deps): bump google.golang.org/grpc from 1.83.1 to 1.83.2 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.83.1 to 1.83.2. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.83.1...v1.83.2) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.83.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 56eec2aa4..8993e16f8 100644 --- a/go.mod +++ b/go.mod @@ -167,7 +167,7 @@ require ( go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect golang.org/x/mod v0.38.0 // indirect - golang.org/x/net v0.57.0 // indirect + golang.org/x/net v0.58.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect @@ -175,7 +175,7 @@ require ( golang.org/x/tools v0.48.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/grpc v1.83.1 // indirect + google.golang.org/grpc v1.83.2 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index dbea692af..89c4fed54 100644 --- a/go.sum +++ b/go.sum @@ -429,8 +429,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= -golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -496,8 +496,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1: google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= -google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= +google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= +google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From 402938cd0f512e9b137749dd71f816af9fb8fe72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Sep 2026 16:37:42 -0600 Subject: [PATCH 557/571] chore(deps): bump github.com/fluxcd/cli-utils from 1.2.3 to 1.3.0 (#32639) Bumps [github.com/fluxcd/cli-utils](https://github.com/fluxcd/cli-utils) from 1.2.3 to 1.3.0. - [Release notes](https://github.com/fluxcd/cli-utils/releases) - [Commits](https://github.com/fluxcd/cli-utils/compare/v1.2.3...v1.3.0) --- updated-dependencies: - dependency-name: github.com/fluxcd/cli-utils dependency-version: 1.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 50 +++++++++++++------------- go.sum | 112 ++++++++++++++++++++++++++++----------------------------- 2 files changed, 81 insertions(+), 81 deletions(-) diff --git a/go.mod b/go.mod index 8993e16f8..6deadc0d5 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 github.com/extism/go-sdk v1.7.1 github.com/fatih/color v1.19.0 - github.com/fluxcd/cli-utils v1.2.3 + github.com/fluxcd/cli-utils v1.3.0 github.com/foxcpp/go-mockdns v1.2.0 github.com/gobwas/glob v1.0.0 github.com/gofrs/flock v0.13.1 @@ -76,28 +76,28 @@ require ( github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fxamacker/cbor/v2 v2.9.1 // indirect + github.com/fxamacker/cbor/v2 v2.9.3 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect - github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v1.0.0 // indirect - github.com/go-openapi/jsonreference v1.0.0 // indirect - github.com/go-openapi/swag v0.27.1 // indirect - github.com/go-openapi/swag/cmdutils v0.27.1 // indirect - github.com/go-openapi/swag/conv v0.27.1 // indirect - github.com/go-openapi/swag/fileutils v0.27.1 // indirect - github.com/go-openapi/swag/jsonutils v0.27.1 // indirect - github.com/go-openapi/swag/loading v0.27.1 // indirect - github.com/go-openapi/swag/mangling v0.27.1 // indirect - github.com/go-openapi/swag/netutils v0.27.1 // indirect - github.com/go-openapi/swag/pools v0.27.1 // indirect - github.com/go-openapi/swag/stringutils v0.27.1 // indirect - github.com/go-openapi/swag/typeutils v0.27.1 // indirect - github.com/go-openapi/swag/yamlutils v0.27.1 // indirect + github.com/go-openapi/jsonreference v1.0.1 // indirect + github.com/go-openapi/swag v0.29.1 // indirect + github.com/go-openapi/swag/cmdutils v0.29.1 // indirect + github.com/go-openapi/swag/conv v0.29.1 // indirect + github.com/go-openapi/swag/fileutils v0.29.1 // indirect + github.com/go-openapi/swag/jsonutils v0.29.1 // indirect + github.com/go-openapi/swag/loading v0.29.1 // indirect + github.com/go-openapi/swag/mangling v0.29.1 // indirect + github.com/go-openapi/swag/netutils v0.29.1 // indirect + github.com/go-openapi/swag/pools v0.29.1 // indirect + github.com/go-openapi/swag/stringutils v0.29.1 // indirect + github.com/go-openapi/swag/typeutils v0.29.1 // indirect + github.com/go-openapi/swag/yamlutils v0.29.1 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect @@ -109,7 +109,7 @@ require ( github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.19.0 // indirect + github.com/klauspost/compress v1.19.1 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect @@ -127,11 +127,11 @@ require ( github.com/onsi/gomega v1.42.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.24.0 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.70.0 // indirect + github.com/prometheus/client_golang v1.24.1 // indirect + github.com/prometheus/client_model v0.6.3 // indirect + github.com/prometheus/common v0.71.0 // indirect github.com/prometheus/otlptranslator v1.0.0 // indirect - github.com/prometheus/procfs v0.21.1 // indirect + github.com/prometheus/procfs v0.22.0 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect github.com/redis/go-redis/v9 v9.7.3 // indirect @@ -176,13 +176,13 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/grpc v1.83.2 // indirect - google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect + google.golang.org/protobuf v1.36.12 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/component-base v0.37.0 // indirect - k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect - k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect + k8s.io/kube-openapi v0.0.0-20260821135717-be32def86098 // indirect + k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.21.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect diff --git a/go.sum b/go.sum index 89c4fed54..cd2e7afa5 100644 --- a/go.sum +++ b/go.sum @@ -93,14 +93,14 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/cli-utils v1.2.3 h1:RcBLij09aROVOtFWzQzg2My6S68R/sIm4OfQLTXAIWE= -github.com/fluxcd/cli-utils v1.2.3/go.mod h1:RuUFNFaDCsyCWzX4ghxt54rjmyyhNWaUlUFVa/kQq2Q= +github.com/fluxcd/cli-utils v1.3.0 h1:doEjNgeNb4klO/1mNgU9bkeou5zH/YpHBQqZ7HTBgkc= +github.com/fluxcd/cli-utils v1.3.0/go.mod h1:qW5YGrml58qvAv+fBqp9Vp4xGkkIc6h6eJLd77eqFoM= github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= -github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fxamacker/cbor/v2 v2.9.3 h1:oQBnFATpNdY8gJHTndDDv5Xl4QqNaz51G5LLEPhng3Q= +github.com/fxamacker/cbor/v2 v2.9.3/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= @@ -111,46 +111,46 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= -github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= -github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= -github.com/go-openapi/swag v0.27.1 h1:VotvOLWW8q/EAxB0YdsBBGC8XYyeL1YwBj2ungAGPNg= -github.com/go-openapi/swag v0.27.1/go.mod h1:GTkJPwHfhJp6MWr4/rCh64HVI3Ofu+tcsbfjfHmTxpE= -github.com/go-openapi/swag/cmdutils v0.27.1 h1:I7sYqaWVl5mq0NEmNQkAmFDyNin9ufvMX/p2zwtQaOE= -github.com/go-openapi/swag/cmdutils v0.27.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= -github.com/go-openapi/swag/conv v0.27.1 h1:8wi9ZG+olmY1wXphl93EWniPtbSPkXM/feH7FgjsvrU= -github.com/go-openapi/swag/conv v0.27.1/go.mod h1:QbqMivkpKhC3g1B1GGGOJ6ANewI3S62dbzYu3Duowqs= -github.com/go-openapi/swag/fileutils v0.27.1 h1:QQqBSoi5mW4XpU85nS0mLcA+zAE6vLzrb0QkmLKf9oM= -github.com/go-openapi/swag/fileutils v0.27.1/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= -github.com/go-openapi/swag/jsonutils v0.27.1 h1:SVgK3i4USzCU5mibOOS/l4ea2h9UQXy7J7RNLTjuXjU= -github.com/go-openapi/swag/jsonutils v0.27.1/go.mod h1:tdlEpZqdcQ17uj6J4YdK9vd8It5qWMwjWXOs0tjpRlk= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1 h1:mJu3COL9WEaZVp/Kf2PRMi7tPszPEJfSr/OO75ynCs8= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= -github.com/go-openapi/swag/loading v0.27.1 h1:/DxUgDXKbBX4bcn7r9uEXfJyzN5XpiJmZplzQTjrRCY= -github.com/go-openapi/swag/loading v0.27.1/go.mod h1:jvGh3iA2+zyUUycB5fgJWzeHnhrpvGnJJM0RVE9ZShE= -github.com/go-openapi/swag/mangling v0.27.1 h1:yC9D0HyUE8gbP+BfmGx9+AA89ikwZTMjESK3OnnoaqA= -github.com/go-openapi/swag/mangling v0.27.1/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= -github.com/go-openapi/swag/netutils v0.27.1 h1:mICMFoS82F5TZ4Zy3cqmcQk+BFeCp3Uyq3Np7GI0/qU= -github.com/go-openapi/swag/netutils v0.27.1/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= -github.com/go-openapi/swag/pools v0.27.1 h1:9LeadcMyb2GJCbXX5hVQDbZ2Lq9TL4dCs/nx1j5DO0E= -github.com/go-openapi/swag/pools v0.27.1/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= -github.com/go-openapi/swag/stringutils v0.27.1 h1:ZXePZ0r2p1qSjo8tD3Un4vFj8+FqlCkczxDrJIhYUp8= -github.com/go-openapi/swag/stringutils v0.27.1/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= -github.com/go-openapi/swag/typeutils v0.27.1 h1:KSTdFlfnse4r6dP9IrEnwMldjE+zs71UeEB3//PtVXc= -github.com/go-openapi/swag/typeutils v0.27.1/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= -github.com/go-openapi/swag/yamlutils v0.27.1 h1:ftxv6xvXb1E3zohUc+okZ9nSqNb9StQX/FXnKZ98sQA= -github.com/go-openapi/swag/yamlutils v0.27.1/go.mod h1:bnxFIB1qewGRiZHypXGZ3fNgf13/0HfRgnS/iZBDrOo= -github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= -github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= -github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= -github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-openapi/jsonreference v1.0.1 h1:4zJ7AmYDKNmD3aSpfPnFNCFA5E80/xMHUNKgydaLh38= +github.com/go-openapi/jsonreference v1.0.1/go.mod h1:dYplQXa6p5lXprLcJ8LE2iU7vNpXsAHDQ5ZAgL+Qx3A= +github.com/go-openapi/swag v0.29.1 h1:C6EeWzUwQtcWEhE9eqBdUubGXxhWY4PlzHMLD7kLaiQ= +github.com/go-openapi/swag v0.29.1/go.mod h1:BzxEXKiPlSXRsRTv1KSBF/BpGKHxA/YciCnr4tv9bvA= +github.com/go-openapi/swag/cmdutils v0.29.1 h1:3DorPGfUdE80BogKY22EzoHBcHMrkVomZMoV7kS4ANY= +github.com/go-openapi/swag/cmdutils v0.29.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.29.1 h1:AC4Eh/5c/eUDOUCzzsRC9ghmFgOSBHeRMGIngY0ZUGA= +github.com/go-openapi/swag/conv v0.29.1/go.mod h1:S1X7/ZrBEZOC0Wc8AGxjbcGS92l3WEjA7aPtpl+RaqM= +github.com/go-openapi/swag/fileutils v0.29.1 h1:ZcPzMceVhU1WPbK6N1G6sNQKdd1CWJlf3cA08UHuoM0= +github.com/go-openapi/swag/fileutils v0.29.1/go.mod h1:/wofKYckbtRl2p3+EwQsosie5CT1B38+dQ+PS579BzI= +github.com/go-openapi/swag/jsonutils v0.29.1 h1:AFCxs0eQZ24/QyfhVHM2t49rMz7Vv3XCsZQI6yrNy+c= +github.com/go-openapi/swag/jsonutils v0.29.1/go.mod h1:u3+sCfJpttDpcmS5kpm0yxL6GK0eWgODsx8Yw8fcqNM= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.1 h1:BiiXE31Bx9SfpsMmOQj5KYpUhTZBpLVriVhJDuLuY2o= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.1/go.mod h1:julgTUKZ9/D0j6O7GKajmRs+812FWxQg/mMpGunWSjg= +github.com/go-openapi/swag/loading v0.29.1 h1:FCv5fG8UhTdDJa2R7w+5O9Ekpcbw7tt0nFWvmDKGBjc= +github.com/go-openapi/swag/loading v0.29.1/go.mod h1:N0ESuem4p2oedKal8EJhciqnJ9Q9Wmt83L1CRB3Fouw= +github.com/go-openapi/swag/mangling v0.29.1 h1:lHALtvYCdxVnRl4GrHmFPwfBTZYIObqdGNSKyu/8D6I= +github.com/go-openapi/swag/mangling v0.29.1/go.mod h1:SAop9pB7PUjQ/CGCNf/JmCKTRK+GDO+RqE9UHqC/N6s= +github.com/go-openapi/swag/netutils v0.29.1 h1:IjIvdEP5duKcghFqJEPSUraRnkKYHoM65kTluTu+Jb4= +github.com/go-openapi/swag/netutils v0.29.1/go.mod h1:DUde7x4Bx00k5jYl2AdRpNAO0m7atUvD2x6X+bWkbno= +github.com/go-openapi/swag/pools v0.29.1 h1:NRogYxdEW9SjRM4mkAOji9iefO4MRXq3p/ZJcoQbUKg= +github.com/go-openapi/swag/pools v0.29.1/go.mod h1:leDcaghjkRAhCuCRv9NfJU5f0mjoU3cT/XZObhMk3pc= +github.com/go-openapi/swag/stringutils v0.29.1 h1:1ykunK7iJQk1uOO7+oUH1ukbsK85fFCOiCFMOVSY+F0= +github.com/go-openapi/swag/stringutils v0.29.1/go.mod h1:7fSqZ+z8Qc0tOfAAK0jVa5qFGrnIlRi6n7NeGGrr1vc= +github.com/go-openapi/swag/typeutils v0.29.1 h1:Nzv9nhnlLCRBPQqfOX+7lB6Guju370or8StT+lIOf6M= +github.com/go-openapi/swag/typeutils v0.29.1/go.mod h1:hxpgDZJVBkBsi/d3MIUosafoFdE5exaQRmVp0zwu3YE= +github.com/go-openapi/swag/yamlutils v0.29.1 h1:69w3tsBajm7MR/fejLy7HD/3J68Ys1SeeZMEzZ3w2sk= +github.com/go-openapi/swag/yamlutils v0.29.1/go.mod h1:rgsp3vT/QdWzKwn43CigDwjOGIenPyTZMKnxEM8jZOA= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.1 h1:Jm+/ze2rMtbD98yen92AhATGLGREDYXG56Xr4gMjEtE= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.1/go.mod h1:YDPnwCRDu38/oJBVMBVXOUDiJ9cIeBHWvfImHaXqnv4= +github.com/go-openapi/testify/v2 v2.6.1 h1:6CNJhTjMzgaeaH8WhshcsZNPIvRemiOcFpU7seO/y7Q= +github.com/go-openapi/testify/v2 v2.6.1/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -168,8 +168,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -204,8 +204,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= -github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ= -github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk= +github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -276,23 +276,23 @@ github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjz github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI= -github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE= +github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU= +github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/client_model v0.6.3 h1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo= +github.com/prometheus/client_model v0.6.3/go.mod h1:gpN5P9S7Rr6Yr92PiQ+Ixvhf6JZEkF1dnxsYL2aPBEM= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.70.0 h1:bcpru3tWPVnxGnETLgOV5jbp/JRXgYEyv65CuBLAMMI= -github.com/prometheus/common v0.70.0/go.mod h1:S/SFasQmgGiYH6C81LKCtYa8QACgthGg5zxL2udV7SY= +github.com/prometheus/common v0.71.0 h1:9KDAKb7Mj3HEVKyFCK6Dc/HIwlBzZIN2l7/lrHl3KK8= +github.com/prometheus/common v0.71.0/go.mod h1:CLJ5H8TEsGX8bl31BdMkfhIZ+QmZ9tBPPotUxUbfcmk= github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= -github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= +github.com/prometheus/procfs v0.22.0 h1:6q9+/JL9IKAPbCmBrv9n5O5Ty3NKnciV5X7YGw0oics= +github.com/prometheus/procfs v0.22.0/go.mod h1:CvmFr/GVhIjIvWJZW3tgkODBQMRIf0EyWMQLHCHab58= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= @@ -498,8 +498,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= -google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= -google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -530,12 +530,12 @@ k8s.io/component-base v0.37.0 h1:3SdSa4+itMdFTDFTeR8CxKGmSTSMXFlKL4ky8OqjguM= k8s.io/component-base v0.37.0/go.mod h1:LjOebp4R9y6LODWZQv102ZQxGheLcDO2ZJLAw6bbh4I= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= -k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= +k8s.io/kube-openapi v0.0.0-20260821135717-be32def86098 h1:z5+pcu1jTyKK5mNTe2/+x+U6Uuv9jRVOJQLaBJJMpeI= +k8s.io/kube-openapi v0.0.0-20260821135717-be32def86098/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= k8s.io/kubectl v0.37.0 h1:cici6hiofx93ASldmprDmZF55SfhVt4o3HniltVLjTc= k8s.io/kubectl v0.37.0/go.mod h1:RSeEl8e/yqDx6srG8Azr0uAtVPNIZljA0PNh9HCBcdg= -k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= -k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE= +k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM= oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4= sigs.k8s.io/controller-runtime v0.25.0 h1:44KgRUPew331KSJpNu8zJow3iTR5W0p/SfrHdw3lV40= From c90810dda47081ecd144ad44d20867e26a40338f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Sep 2026 16:46:14 -0600 Subject: [PATCH 558/571] chore(deps): bump golang.org/x/text from 0.41.0 to 0.42.0 (#32641) Bumps [golang.org/x/text](https://github.com/golang/text) from 0.41.0 to 0.42.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.41.0...v0.42.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.42.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 6deadc0d5..671356f89 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( go.yaml.in/yaml/v3 v3.0.5 golang.org/x/crypto v0.56.0 golang.org/x/term v0.45.0 - golang.org/x/text v0.41.0 + golang.org/x/text v0.42.0 k8s.io/api v0.37.0 k8s.io/apiextensions-apiserver v0.37.0 k8s.io/apimachinery v0.37.0 @@ -166,13 +166,13 @@ require ( go.opentelemetry.io/otel/trace v1.44.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect - golang.org/x/mod v0.38.0 // indirect + golang.org/x/mod v0.41.0 // indirect golang.org/x/net v0.58.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.22.0 // indirect + golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.48.0 // indirect + golang.org/x/tools v0.49.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/grpc v1.83.2 // indirect diff --git a/go.sum b/go.sum index cd2e7afa5..04e4137ce 100644 --- a/go.sum +++ b/go.sum @@ -417,8 +417,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= -golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -441,8 +441,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -477,8 +477,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -487,8 +487,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= From 0d4048c62a09ec1fcda665fd85e039c3a0e0149c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Sep 2026 17:03:58 -0600 Subject: [PATCH 559/571] chore(deps): bump golang.org/x/term from 0.45.0 to 0.46.0 (#32643) Bumps [golang.org/x/term](https://github.com/golang/term) from 0.45.0 to 0.46.0. - [Commits](https://github.com/golang/term/compare/v0.45.0...v0.46.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-version: 0.46.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 671356f89..762c5ed80 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/tetratelabs/wazero v1.12.0 go.yaml.in/yaml/v3 v3.0.5 golang.org/x/crypto v0.56.0 - golang.org/x/term v0.45.0 + golang.org/x/term v0.46.0 golang.org/x/text v0.42.0 k8s.io/api v0.37.0 k8s.io/apiextensions-apiserver v0.37.0 @@ -170,7 +170,7 @@ require ( golang.org/x/net v0.58.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.23.0 // indirect - golang.org/x/sys v0.47.0 // indirect + golang.org/x/sys v0.48.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.49.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect diff --git a/go.sum b/go.sum index 04e4137ce..c1ed07f29 100644 --- a/go.sum +++ b/go.sum @@ -459,8 +459,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -468,8 +468,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= -golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= +golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= From bfa3b6c325124383d31bc43532eff56c65f9a4a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Sep 2026 17:11:48 -0600 Subject: [PATCH 560/571] chore(deps): bump golang.org/x/crypto from 0.56.0 to 0.57.0 (#32645) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.56.0 to 0.57.0. - [Commits](https://github.com/golang/crypto/compare/v0.56.0...v0.57.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.57.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 762c5ed80..87520d3c4 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/stretchr/testify v1.12.1 github.com/tetratelabs/wazero v1.12.0 go.yaml.in/yaml/v3 v3.0.5 - golang.org/x/crypto v0.56.0 + golang.org/x/crypto v0.57.0 golang.org/x/term v0.46.0 golang.org/x/text v0.42.0 k8s.io/api v0.37.0 diff --git a/go.sum b/go.sum index c1ed07f29..d9b0f3d02 100644 --- a/go.sum +++ b/go.sum @@ -411,8 +411,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= -golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= From 3e382d022ae8bda659b77e1f2be2fb7b9cb4127f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 17:17:13 -0600 Subject: [PATCH 561/571] chore(deps): bump the github-actions group with 4 updates (#32649) Bumps the github-actions group with 4 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action). Updates `github/codeql-action/init` from 4.37.9 to 4.38.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/cdf488f595d80d6e07e03d4674febd5ab45fa938...b96794f015dfd88f77b49b1c93e0fa7110f94c63) Updates `github/codeql-action/autobuild` from 4.37.9 to 4.38.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/cdf488f595d80d6e07e03d4674febd5ab45fa938...b96794f015dfd88f77b49b1c93e0fa7110f94c63) Updates `github/codeql-action/analyze` from 4.37.9 to 4.38.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/cdf488f595d80d6e07e03d4674febd5ab45fa938...b96794f015dfd88f77b49b1c93e0fa7110f94c63) Updates `github/codeql-action/upload-sarif` from 4.37.9 to 4.38.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/cdf488f595d80d6e07e03d4674febd5ab45fa938...b96794f015dfd88f77b49b1c93e0fa7110f94c63) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.38.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: github/codeql-action/autobuild dependency-version: 4.38.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: github/codeql-action/analyze dependency-version: 4.38.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.38.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 37e3e2048..c20fa58a8 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # pinv4.37.9 + uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # pinv4.38.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@cdf488f595d80d6e07e03d4674febd5ab45fa938 # pinv4.37.9 + uses: github/codeql-action/autobuild@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # pinv4.38.0 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # pinv4.37.9 + uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # pinv4.38.0 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 469178ced..09e0e8030 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 + uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 with: sarif_file: results.sarif From 31050af08853b4e00503a3b732bc964030ed7d81 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2026 06:15:36 -0600 Subject: [PATCH 562/571] chore(deps): bump github.com/mattn/go-shellwords from 1.0.14 to 1.0.15 (#32652) Bumps [github.com/mattn/go-shellwords](https://github.com/mattn/go-shellwords) from 1.0.14 to 1.0.15. - [Release notes](https://github.com/mattn/go-shellwords/releases) - [Commits](https://github.com/mattn/go-shellwords/compare/v1.0.14...v1.0.15) --- updated-dependencies: - dependency-name: github.com/mattn/go-shellwords dependency-version: 1.0.15 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 87520d3c4..81cac2580 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/gosuri/uitable v0.0.4 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.12.3 - github.com/mattn/go-shellwords v1.0.14 + github.com/mattn/go-shellwords v1.0.15 github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 diff --git a/go.sum b/go.sum index d9b0f3d02..3c21684ff 100644 --- a/go.sum +++ b/go.sum @@ -229,8 +229,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-shellwords v1.0.14 h1:yUKzIgsCnosndOASY6/enly1EAuaXeFSQ7cdyA3OuYg= -github.com/mattn/go-shellwords v1.0.14/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-shellwords v1.0.15 h1:rx0n8+ZdM9JWZMlr2BMPAjtLU0rfluLNtwMC2FJOTtY= +github.com/mattn/go-shellwords v1.0.15/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= From c3bb7ad28ef20c5d2aedc07ed89abeb0dae7d1e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 05:50:53 -0600 Subject: [PATCH 563/571] chore(deps): bump sigs.k8s.io/controller-runtime from 0.25.0 to 0.25.1 (#32654) Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.25.0 to 0.25.1. - [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.25.0...v0.25.1) --- updated-dependencies: - dependency-name: sigs.k8s.io/controller-runtime dependency-version: 0.25.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 81cac2580..9bf87cc87 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( k8s.io/klog/v2 v2.140.0 k8s.io/kubectl v0.37.0 oras.land/oras-go/v2 v2.6.2 - sigs.k8s.io/controller-runtime v0.25.0 + sigs.k8s.io/controller-runtime v0.25.1 sigs.k8s.io/kustomize/kyaml v0.21.1 sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index 3c21684ff..76878be07 100644 --- a/go.sum +++ b/go.sum @@ -538,8 +538,8 @@ k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0x k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM= oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo= oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4= -sigs.k8s.io/controller-runtime v0.25.0 h1:44KgRUPew331KSJpNu8zJow3iTR5W0p/SfrHdw3lV40= -sigs.k8s.io/controller-runtime v0.25.0/go.mod h1:4QqLdT6z/L6Olj8JJCtvztid4/fnIiYsfaTFScegctc= +sigs.k8s.io/controller-runtime v0.25.1 h1:BKgU9OeE8xv8EbbM8cY0NVzTQs35rokkdq1jh12fMb4= +sigs.k8s.io/controller-runtime v0.25.1/go.mod h1:4QqLdT6z/L6Olj8JJCtvztid4/fnIiYsfaTFScegctc= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= From 37c258a8f407df81b097040e54ba652491c5268c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:50:25 -0600 Subject: [PATCH 564/571] chore(deps): bump go.opentelemetry.io/otel/sdk from 1.44.0 to 1.45.0 (#32662) Bumps [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go) from 1.44.0 to 1.45.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.44.0...v1.45.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/sdk dependency-version: 1.45.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 24 ++++++++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 9bf87cc87..df65365e2 100644 --- a/go.mod +++ b/go.mod @@ -146,7 +146,7 @@ require ( go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect - go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel v1.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect @@ -159,11 +159,11 @@ require ( go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect go.opentelemetry.io/otel/log v0.19.0 // indirect - go.opentelemetry.io/otel/metric v1.44.0 // indirect - go.opentelemetry.io/otel/sdk v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.45.0 // indirect + go.opentelemetry.io/otel/sdk v1.45.0 // indirect go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect - go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.45.0 // indirect + go.opentelemetry.io/otel/trace v1.45.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect golang.org/x/mod v0.41.0 // indirect diff --git a/go.sum b/go.sum index 76878be07..73496d9fa 100644 --- a/go.sum +++ b/go.sum @@ -352,8 +352,8 @@ go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9a go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= +go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= @@ -378,20 +378,20 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1J go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= -go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= +go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= +go.opentelemetry.io/otel/metric/x v0.67.0 h1:PcicCNZFkZ4bXfSooXdo3WN7RBOVOtjVdo1wD358Uns= +go.opentelemetry.io/otel/metric/x v0.67.0/go.mod h1:FBjCWZe6wgcqxcMtjdGiClDKXb2YxxXii0CXftE4QtI= +go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= +go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk= go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= +go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= +go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= +go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= From 855985a0c6cec65bae6e4f07f6ed53924ddb412a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:58:27 -0600 Subject: [PATCH 565/571] chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace (#32663) Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace](https://github.com/open-telemetry/opentelemetry-go) from 1.44.0 to 1.45.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.44.0...v1.45.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace dependency-version: 1.45.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index df65365e2..0d0505aef 100644 --- a/go.mod +++ b/go.mod @@ -151,7 +151,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect @@ -164,7 +164,7 @@ require ( go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.45.0 // indirect go.opentelemetry.io/otel/trace v1.45.0 // indirect - go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.opentelemetry.io/proto/otlp v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect golang.org/x/mod v0.41.0 // indirect golang.org/x/net v0.58.0 // indirect @@ -173,8 +173,8 @@ require ( golang.org/x/sys v0.48.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.49.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260720211330-0afa2a65878a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260720211330-0afa2a65878a // indirect google.golang.org/grpc v1.83.2 // indirect google.golang.org/protobuf v1.36.12 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect diff --git a/go.sum b/go.sum index 73496d9fa..fe2063112 100644 --- a/go.sum +++ b/go.sum @@ -362,8 +362,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdK go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 h1:QRefszxJmfPdjXUUm3j6iDzY03mTPXMjqErFqQ67vUg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0/go.mod h1:Tiz03lTBVBrm7eWZBOidzEaYaJa8tjwGUGv6d8mlTyk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= @@ -392,8 +392,8 @@ go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJj go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= -go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk= +go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -492,10 +492,10 @@ golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/api v0.0.0-20260720211330-0afa2a65878a h1:97PfJ4tCxY5C7NzzgGqQEMZmXbISdvSArNNEOoUGKBg= +google.golang.org/genproto/googleapis/api v0.0.0-20260720211330-0afa2a65878a/go.mod h1:1brfde68Npq6+WA75c1EHWPijZEG1kMus61ygPZfn4A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260720211330-0afa2a65878a h1:qI/YMH1ep2qQtqcp00gMQyoU7mjvbhg88GJKCvfoLj0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260720211330-0afa2a65878a/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= From c378ef66c877bd466ed3f750ab0b9b6778412489 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:04:33 -0600 Subject: [PATCH 566/571] chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc (#32664) Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://github.com/open-telemetry/opentelemetry-go) from 1.44.0 to 1.45.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.44.0...v1.45.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc dependency-version: 1.45.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 0d0505aef..5ff21bda5 100644 --- a/go.mod +++ b/go.mod @@ -152,7 +152,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect @@ -173,8 +173,8 @@ require ( golang.org/x/sys v0.48.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.49.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260720211330-0afa2a65878a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260720211330-0afa2a65878a // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect google.golang.org/grpc v1.83.2 // indirect google.golang.org/protobuf v1.36.12 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect diff --git a/go.sum b/go.sum index fe2063112..25a8e18d3 100644 --- a/go.sum +++ b/go.sum @@ -364,8 +364,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 h1:QRefszxJmfPdjXUUm3j6iDzY03mTPXMjqErFqQ67vUg= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0/go.mod h1:Tiz03lTBVBrm7eWZBOidzEaYaJa8tjwGUGv6d8mlTyk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 h1:fG5MCxGz8+2VtrN/WgqSpJFctVz24gpxj8CxkKmc8Ww= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0/go.mod h1:BmAYTn+3ysbRe+IU2msxmf5Rx3g6DHvex+tWI3LdhYI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs= @@ -492,10 +492,10 @@ golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260720211330-0afa2a65878a h1:97PfJ4tCxY5C7NzzgGqQEMZmXbISdvSArNNEOoUGKBg= -google.golang.org/genproto/googleapis/api v0.0.0-20260720211330-0afa2a65878a/go.mod h1:1brfde68Npq6+WA75c1EHWPijZEG1kMus61ygPZfn4A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260720211330-0afa2a65878a h1:qI/YMH1ep2qQtqcp00gMQyoU7mjvbhg88GJKCvfoLj0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260720211330-0afa2a65878a/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc= +google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= From 7fd3c5e8f2f096573b86a76fa5682ef905778c5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:12:52 -0600 Subject: [PATCH 567/571] chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp (#32665) Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp](https://github.com/open-telemetry/opentelemetry-go) from 1.43.0 to 1.45.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.43.0...v1.45.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp dependency-version: 1.45.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5ff21bda5..ca369dd10 100644 --- a/go.mod +++ b/go.mod @@ -153,7 +153,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect diff --git a/go.sum b/go.sum index 25a8e18d3..b5748c9c8 100644 --- a/go.sum +++ b/go.sum @@ -366,8 +366,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 h1:QRefszxJmfPdjXUUm3j go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0/go.mod h1:Tiz03lTBVBrm7eWZBOidzEaYaJa8tjwGUGv6d8mlTyk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 h1:fG5MCxGz8+2VtrN/WgqSpJFctVz24gpxj8CxkKmc8Ww= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0/go.mod h1:BmAYTn+3ysbRe+IU2msxmf5Rx3g6DHvex+tWI3LdhYI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 h1:QBajQ2SrwQijzHyZbQlPsuIzpl/ll8DY6wPWsajeGcI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0/go.mod h1:08ZQLjrPLQ6R4kAXvuOvODEer5Yh4CoFvll5qB2BCI8= go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs= go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s= From e0f9bdac2865764b1ae58fc44efc488ccdbf96ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:33:07 +0000 Subject: [PATCH 568/571] chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc Bumps [go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc](https://github.com/open-telemetry/opentelemetry-go) from 0.18.0 to 0.21.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v0.18.0...log/v0.21.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc dependency-version: 0.21.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index ca369dd10..04607a453 100644 --- a/go.mod +++ b/go.mod @@ -147,7 +147,7 @@ require ( go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect go.opentelemetry.io/otel v1.45.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect @@ -158,10 +158,10 @@ require ( go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect - go.opentelemetry.io/otel/log v0.19.0 // indirect + go.opentelemetry.io/otel/log v0.21.0 // indirect go.opentelemetry.io/otel/metric v1.45.0 // indirect go.opentelemetry.io/otel/sdk v1.45.0 // indirect - go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.21.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.45.0 // indirect go.opentelemetry.io/otel/trace v1.45.0 // indirect go.opentelemetry.io/proto/otlp v1.11.0 // indirect diff --git a/go.sum b/go.sum index b5748c9c8..588486e68 100644 --- a/go.sum +++ b/go.sum @@ -354,8 +354,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0 h1:WseeVYf5dJZTsyPiyW5L14k5qsSibqXAMTSiFEDiWr0= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0/go.mod h1:SiLZnQS6Qk2eCpvr2CH/XMAOa64TWGXxEZJZCpD2Lmc= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0/go.mod h1:ji9vId85hMxqfvICA0Jt8JqEdrXaAkcpkI9HPXya0ro= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk= @@ -376,18 +376,18 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPf go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= -go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= -go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= +go.opentelemetry.io/otel/log v0.21.0 h1:SLsVDGmtyBrdw8/a2Z0bOIxou/+bN4z56GebH7T0LvA= +go.opentelemetry.io/otel/log v0.21.0/go.mod h1:iReetQrZL9Wyg84cCkOoCmqDHS5RCFfyxC7J+r8fn8g= go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= go.opentelemetry.io/otel/metric/x v0.67.0 h1:PcicCNZFkZ4bXfSooXdo3WN7RBOVOtjVdo1wD358Uns= go.opentelemetry.io/otel/metric/x v0.67.0/go.mod h1:FBjCWZe6wgcqxcMtjdGiClDKXb2YxxXii0CXftE4QtI= go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= -go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= -go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk= -go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk= +go.opentelemetry.io/otel/sdk/log v0.21.0 h1:QsE7XSR0ktQdKmRKGnR+f1ObGF32WG+7MER/P9KgmYc= +go.opentelemetry.io/otel/sdk/log v0.21.0/go.mod h1:m9mApjCoD2/1QuKCAptjv+BrG9WKOvQLVdNx+iBldTo= +go.opentelemetry.io/otel/sdk/log/logtest v0.21.0 h1:X+JBBgKlswCGYsmgL0CnoUUtlE//VB345c84jYAYkdQ= +go.opentelemetry.io/otel/sdk/log/logtest v0.21.0/go.mod h1:HD1575K8e6sIFBBDd5tZB3t9DlMytWXq9FuR+Y4rfjE= go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= From 6f5c2f5f72c5dce94c0186856067fcbd65a3574f Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Mon, 21 Sep 2026 13:38:27 +0100 Subject: [PATCH 569/571] feat(storage): add internal/storage/driver for release v2 (#32667) * feat(storage): add internal/storage/driver for release v2 Copy of pkg/storage/driver operating on internal/release/v2, with the storage object Type field set to helm.sh/release.v2. The sh.helm.release.v1 name prefix is unchanged. Signed-off-by: Evans Mungai * feat(storage): mark configmap release schema with a label ConfigMaps have no Type field to record it in, unlike Secrets. Absence of the label means v1. Also documents why the sh.helm.release.v1 name prefix is decoupled from Type. Signed-off-by: Evans Mungai * Update internal/storage/driver/sql.go Co-authored-by: Terry Howe Signed-off-by: Evans Mungai * Update test Signed-off-by: Evans Mungai * PR comment fixes - Add comment to explain sql driver update behaviour when it comes to updating custom labels - Apply custom labels in memory driver Signed-off-by: Evans Mungai * Update internal/storage/driver/records.go Co-authored-by: Terry Howe Signed-off-by: Evans Mungai --------- Signed-off-by: Evans Mungai Co-authored-by: Terry Howe --- internal/storage/driver/cfgmaps.go | 290 +++++++++ internal/storage/driver/cfgmaps_test.go | 231 ++++++++ internal/storage/driver/driver.go | 120 ++++ internal/storage/driver/labels.go | 48 ++ internal/storage/driver/labels_test.go | 55 ++ internal/storage/driver/memory.go | 255 ++++++++ internal/storage/driver/memory_test.go | 266 +++++++++ internal/storage/driver/mock_test.go | 267 +++++++++ internal/storage/driver/records.go | 127 ++++ internal/storage/driver/records_test.go | 226 ++++++++ internal/storage/driver/secrets.go | 287 +++++++++ internal/storage/driver/secrets_test.go | 198 +++++++ internal/storage/driver/sql.go | 742 ++++++++++++++++++++++++ internal/storage/driver/sql_test.go | 564 ++++++++++++++++++ internal/storage/driver/util.go | 131 +++++ internal/storage/driver/util_test.go | 105 ++++ pkg/storage/driver/records.go | 4 + pkg/storage/driver/sql.go | 14 + pkg/storage/storage.go | 22 +- 19 files changed, 3947 insertions(+), 5 deletions(-) create mode 100644 internal/storage/driver/cfgmaps.go create mode 100644 internal/storage/driver/cfgmaps_test.go create mode 100644 internal/storage/driver/driver.go create mode 100644 internal/storage/driver/labels.go create mode 100644 internal/storage/driver/labels_test.go create mode 100644 internal/storage/driver/memory.go create mode 100644 internal/storage/driver/memory_test.go create mode 100644 internal/storage/driver/mock_test.go create mode 100644 internal/storage/driver/records.go create mode 100644 internal/storage/driver/records_test.go create mode 100644 internal/storage/driver/secrets.go create mode 100644 internal/storage/driver/secrets_test.go create mode 100644 internal/storage/driver/sql.go create mode 100644 internal/storage/driver/sql_test.go create mode 100644 internal/storage/driver/util.go create mode 100644 internal/storage/driver/util_test.go diff --git a/internal/storage/driver/cfgmaps.go b/internal/storage/driver/cfgmaps.go new file mode 100644 index 000000000..73076fdfe --- /dev/null +++ b/internal/storage/driver/cfgmaps.go @@ -0,0 +1,290 @@ +/* +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 driver + +import ( + "context" + "fmt" + "log/slog" + "strconv" + "strings" + "time" + + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kblabels "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/validation" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + + "helm.sh/helm/v4/internal/logging" + rspb "helm.sh/helm/v4/internal/release/v2" + "helm.sh/helm/v4/pkg/release" +) + +var _ Driver = (*ConfigMaps)(nil) + +// ConfigMapsDriverName is the string name of the driver. +const ConfigMapsDriverName = "ConfigMap" + +// ConfigMaps is a wrapper around an implementation of a kubernetes +// ConfigMapsInterface. +type ConfigMaps struct { + impl corev1.ConfigMapInterface + + // Embed a LogHolder to provide logger functionality + logging.LogHolder +} + +// NewConfigMaps initializes a new ConfigMaps wrapping an implementation of +// the kubernetes ConfigMapsInterface. +func NewConfigMaps(impl corev1.ConfigMapInterface) *ConfigMaps { + c := &ConfigMaps{ + impl: impl, + } + c.SetLogger(slog.Default().Handler()) + return c +} + +// Name returns the name of the driver. +func (cfgmaps *ConfigMaps) Name() string { + return ConfigMapsDriverName +} + +// Get fetches the release named by key. The corresponding release is returned +// or error if not found. +func (cfgmaps *ConfigMaps) Get(key string) (release.Releaser, error) { + // fetch the configmap holding the release named by key + obj, err := cfgmaps.impl.Get(context.Background(), key, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + return nil, ErrReleaseNotFound + } + + cfgmaps.Logger().Debug("failed to get release", slog.String("key", key), slog.Any("error", err)) + return nil, err + } + // found the configmap, decode the base64 data string + r, err := decodeRelease(obj.Data["release"]) + if err != nil { + cfgmaps.Logger().Debug("failed to decode data", slog.String("key", key), slog.Any("error", err)) + return nil, err + } + r.Labels = filterSystemLabels(obj.Labels) + // return the release object + return r, nil +} + +// List fetches all releases and returns the list releases such +// that filter(release) == true. An error is returned if the +// configmap fails to retrieve the releases. +func (cfgmaps *ConfigMaps) List(filter func(release.Releaser) bool) ([]release.Releaser, error) { + lsel := kblabels.Set{"owner": "helm"}.AsSelector() + opts := metav1.ListOptions{LabelSelector: lsel.String()} + + list, err := cfgmaps.impl.List(context.Background(), opts) + if err != nil { + cfgmaps.Logger().Debug("failed to list releases", slog.Any("error", err)) + return nil, err + } + + var results []release.Releaser + + // iterate over the configmaps object list + // and decode each release + for _, item := range list.Items { + rls, err := decodeRelease(item.Data["release"]) + if err != nil { + cfgmaps.Logger().Debug("failed to decode release", slog.Any("item", item), slog.Any("error", err)) + continue + } + + rls.Labels = item.Labels + + if filter(rls) { + results = append(results, rls) + } + } + return results, nil +} + +// Query fetches all releases that match the provided map of labels. +// An error is returned if the configmap fails to retrieve the releases. +func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]release.Releaser, error) { + ls := kblabels.Set{} + for k, v := range labels { + if errs := validation.IsValidLabelValue(v); len(errs) != 0 { + return nil, fmt.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; ")) + } + ls[k] = v + } + + opts := metav1.ListOptions{LabelSelector: ls.AsSelector().String()} + + list, err := cfgmaps.impl.List(context.Background(), opts) + if err != nil { + cfgmaps.Logger().Debug("failed to query with labels", slog.Any("error", err)) + return nil, err + } + + if len(list.Items) == 0 { + return nil, ErrReleaseNotFound + } + + var results []release.Releaser + for _, item := range list.Items { + rls, err := decodeRelease(item.Data["release"]) + if err != nil { + cfgmaps.Logger().Debug("failed to decode release", slog.Any("error", err)) + continue + } + rls.Labels = item.Labels + results = append(results, rls) + } + return results, nil +} + +// Create creates a new ConfigMap holding the release. If the +// ConfigMap already exists, ErrReleaseExists is returned. +func (cfgmaps *ConfigMaps) Create(key string, rls release.Releaser) error { + // set labels for configmaps object meta data + var lbs labels + + rac, err := release.NewAccessor(rls) + if err != nil { + return err + } + + lbs.init() + lbs.fromMap(rac.Labels()) + lbs.set("createdAt", strconv.FormatInt(time.Now().Unix(), 10)) + + rel, err := releaserToV1Release(rls) + if err != nil { + return err + } + + // create a new configmap to hold the release + obj, err := newConfigMapsObject(key, rel, lbs) + if err != nil { + cfgmaps.Logger().Debug("failed to encode release", slog.String("name", rac.Name()), slog.Any("error", err)) + return err + } + // push the configmap object out into the kubiverse + if _, err := cfgmaps.impl.Create(context.Background(), obj, metav1.CreateOptions{}); err != nil { + if apierrors.IsAlreadyExists(err) { + return ErrReleaseExists + } + + cfgmaps.Logger().Debug("failed to create release", slog.Any("error", err)) + return err + } + return nil +} + +// Update updates the ConfigMap holding the release. If not found +// the ConfigMap is created to hold the release. +func (cfgmaps *ConfigMaps) Update(key string, rel release.Releaser) error { + // set labels for configmaps object meta data + var lbs labels + + rls, err := releaserToV1Release(rel) + if err != nil { + return err + } + + lbs.init() + lbs.fromMap(rls.Labels) + lbs.set("modifiedAt", strconv.FormatInt(time.Now().Unix(), 10)) + + // create a new configmap object to hold the release + obj, err := newConfigMapsObject(key, rls, lbs) + if err != nil { + cfgmaps.Logger().Debug( + "failed to encode release", + slog.String("name", rls.Name), + slog.Any("error", err), + ) + return err + } + // push the configmap object out into the kubiverse + _, err = cfgmaps.impl.Update(context.Background(), obj, metav1.UpdateOptions{}) + if err != nil { + cfgmaps.Logger().Debug("failed to update release", slog.Any("error", err)) + return err + } + return nil +} + +// Delete deletes the ConfigMap holding the release named by key. +func (cfgmaps *ConfigMaps) Delete(key string) (rls release.Releaser, err error) { + // fetch the release to check existence + if rls, err = cfgmaps.Get(key); err != nil { + return nil, err + } + // delete the release + if err := cfgmaps.impl.Delete(context.Background(), key, metav1.DeleteOptions{}); err != nil { + return rls, err + } + return rls, nil +} + +// newConfigMapsObject constructs a kubernetes ConfigMap object +// to store a release. Each configmap data entry is the base64 +// encoded gzipped string of a release. +// +// The following labels are used within each configmap: +// +// "modifiedAt" - timestamp indicating when this configmap was last modified. (set in Update) +// "createdAt" - timestamp indicating when this configmap was created. (set in Create) +// "version" - version of the release. +// "status" - status of the release (see pkg/release/status.go for variants) +// "owner" - owner of the configmap, currently "helm". +// "name" - name of the release. +// "helm.sh/release-version" - release object schema version, see releaseVersionLabel. +func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*v1.ConfigMap, error) { + const owner = "helm" + + // encode the release + s, err := encodeRelease(rls) + if err != nil { + return nil, err + } + + if lbs == nil { + lbs.init() + } + + // apply custom labels + lbs.fromMap(rls.Labels) + + // apply labels + lbs.set("name", rls.Name) + lbs.set("owner", owner) + lbs.set("status", rls.Info.Status.String()) + lbs.set("version", strconv.Itoa(rls.Version)) + lbs.set(releaseVersionLabel, releaseVersion) + + // create and return configmap object + return &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: key, + Labels: lbs.toMap(), + }, + Data: map[string]string{"release": s}, + }, nil +} diff --git a/internal/storage/driver/cfgmaps_test.go b/internal/storage/driver/cfgmaps_test.go new file mode 100644 index 000000000..5b97e8213 --- /dev/null +++ b/internal/storage/driver/cfgmaps_test.go @@ -0,0 +1,231 @@ +/* +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 driver + +import ( + "encoding/base64" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + + rspb "helm.sh/helm/v4/internal/release/v2" + "helm.sh/helm/v4/pkg/release" + "helm.sh/helm/v4/pkg/release/common" +) + +func TestConfigMapName(t *testing.T) { + c := newTestFixtureCfgMaps(t) + assert.Equal(t, ConfigMapsDriverName, c.Name(), "Expected name to be %q, got %q", ConfigMapsDriverName, c.Name()) +} + +func TestConfigMapGet(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...) + + // get release with key + got, err := cfgmaps.Get(key) + require.NoError(t, err, "Failed to get release") + // compare fetched release with original + assert.Equalf(t, rel, got, "Expected {%v}, got {%v}", rel, got) +} + +func TestUncompressedConfigMapGet(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + // Create a test fixture which contains an uncompressed release + cfgmap, err := newConfigMapsObject(key, rel, nil) + require.NoError(t, err, "Failed to create configmap") + b, err := json.Marshal(rel) + require.NoError(t, err, "Failed to marshal release") + cfgmap.Data["release"] = base64.StdEncoding.EncodeToString(b) + var mock MockConfigMapsInterface + mock.objects = map[string]*v1.ConfigMap{key: cfgmap} + cfgmaps := NewConfigMaps(&mock) + + // get release with key + got, err := cfgmaps.Get(key) + require.NoError(t, err, "Failed to get release") + // compare fetched release with original + assert.Equalf(t, rel, got, "Expected {%v}, got {%v}", rel, got) +} + +func TestConfigMapReleaseVersionLabel(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + cfgmap, err := newConfigMapsObject(key, rel, nil) + require.NoError(t, err, "Failed to create configmap") + + // ConfigMaps have no Type field, so the release schema version is a label. + assert.Equal(t, releaseVersion, cfgmap.Labels[releaseVersionLabel]) + + // The label is a system label and must not leak into the release's own labels. + assert.True(t, isSystemLabel(releaseVersionLabel)) + assert.NotContains(t, filterSystemLabels(cfgmap.Labels), releaseVersionLabel) +} + +func convertReleaserToV1(t *testing.T, rel release.Releaser) *rspb.Release { + t.Helper() + switch r := rel.(type) { + case rspb.Release: + return &r + case *rspb.Release: + return r + case nil: + return nil + } + + t.Fatalf("Unsupported release type: %T", rel) + return nil +} + +func TestConfigMapList(t *testing.T) { + cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{ + releaseStub("key-1", 1, "default", common.StatusUninstalled), + releaseStub("key-2", 1, "default", common.StatusUninstalled), + releaseStub("key-3", 1, "default", common.StatusDeployed), + releaseStub("key-4", 1, "default", common.StatusDeployed), + releaseStub("key-5", 1, "default", common.StatusSuperseded), + releaseStub("key-6", 1, "default", common.StatusSuperseded), + }...) + + // list all deleted releases + del, err := cfgmaps.List(func(rel release.Releaser) bool { + rls := convertReleaserToV1(t, rel) + return rls.Info.Status == common.StatusUninstalled + }) + // check + require.NoError(t, err, "Failed to list deleted") + assert.Len(t, del, 2, "Expected 2 deleted") + + // list all deployed releases + dpl, err := cfgmaps.List(func(rel release.Releaser) bool { + rls := convertReleaserToV1(t, rel) + return rls.Info.Status == common.StatusDeployed + }) + // check + require.NoError(t, err, "Failed to list deployed") + assert.Len(t, dpl, 2, "Expected 2 deployed") + + // list all superseded releases + ssd, err := cfgmaps.List(func(rel release.Releaser) bool { + rls := convertReleaserToV1(t, rel) + return rls.Info.Status == common.StatusSuperseded + }) + // check + require.NoError(t, err, "Failed to list superseded") + assert.Len(t, ssd, 2, "Expected 2 superseded") + // Check if release having both system and custom labels, this is needed to ensure that selector filtering would work. + rls := convertReleaserToV1(t, ssd[0]) + require.Contains(t, rls.Labels, "name", "Expected 'name' label in results, actual %v", rls.Labels) + require.Contains(t, rls.Labels, "key1", "Expected 'key1' label in results, actual %v", rls.Labels) +} + +func TestConfigMapQuery(t *testing.T) { + cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{ + releaseStub("key-1", 1, "default", common.StatusUninstalled), + releaseStub("key-2", 1, "default", common.StatusUninstalled), + releaseStub("key-3", 1, "default", common.StatusDeployed), + releaseStub("key-4", 1, "default", common.StatusDeployed), + releaseStub("key-5", 1, "default", common.StatusSuperseded), + releaseStub("key-6", 1, "default", common.StatusSuperseded), + }...) + + rls, err := cfgmaps.Query(map[string]string{"status": "deployed"}) + require.NoError(t, err, "Failed to query") + assert.Len(t, rls, 2, "Expected 2 results") + + _, err = cfgmaps.Query(map[string]string{"name": "notExist"}) + assert.ErrorIs(t, err, ErrReleaseNotFound) +} + +func TestConfigMapCreate(t *testing.T) { + cfgmaps := newTestFixtureCfgMaps(t) + + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + // store the release in a configmap + require.NoErrorf(t, cfgmaps.Create(key, rel), "Failed to create release with key %q", key) + + // get the release back + got, err := cfgmaps.Get(key) + require.NoError(t, err, "Failed to get release with key %q", key) + + // compare created release with original + assert.Equalf(t, rel, got, "Expected {%v}, got {%v}", rel, got) +} + +func TestConfigMapUpdate(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...) + + // modify release status code + rel.Info.Status = common.StatusSuperseded + + // perform the update + require.NoErrorf(t, cfgmaps.Update(key, rel), "Failed to update release") + + // fetch the updated release + goti, err := cfgmaps.Get(key) + require.NoError(t, err, "Failed to get release with key %q", key) + got := convertReleaserToV1(t, goti) + + // check release has actually been updated by comparing modified fields + assert.Equal(t, got.Info.Status, rel.Info.Status, "Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String()) +} + +func TestConfigMapDelete(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...) + + // perform the delete on a non-existent release + _, err := cfgmaps.Delete("nonexistent") + require.ErrorIs(t, err, ErrReleaseNotFound) + + // perform the delete + rls, err := cfgmaps.Delete(key) + require.NoError(t, err, "Failed to delete release with key %q", key) + assert.Equalf(t, rel, rls, "Expected {%v}, got {%v}", rel, rls) + _, err = cfgmaps.Get(key) + assert.ErrorIs(t, err, ErrReleaseNotFound) +} diff --git a/internal/storage/driver/driver.go b/internal/storage/driver/driver.go new file mode 100644 index 000000000..762fb3d86 --- /dev/null +++ b/internal/storage/driver/driver.go @@ -0,0 +1,120 @@ +/* +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 driver + +import ( + "errors" + "fmt" + + rspb "helm.sh/helm/v4/internal/release/v2" + "helm.sh/helm/v4/pkg/release" +) + +var ( + // ErrReleaseNotFound indicates that a release is not found. + ErrReleaseNotFound = errors.New("release: not found") + // ErrReleaseExists indicates that a release already exists. + ErrReleaseExists = errors.New("release: already exists") + // ErrInvalidKey indicates that a release key could not be parsed. + ErrInvalidKey = errors.New("release: invalid key") + // ErrNoDeployedReleases indicates that there are no releases with the given key in the deployed state + ErrNoDeployedReleases = errors.New("has no deployed releases") +) + +// StorageDriverError records an error and the release name that caused it +type StorageDriverError struct { + ReleaseName string + Err error +} + +func (e *StorageDriverError) Error() string { + return fmt.Sprintf("%q %s", e.ReleaseName, e.Err.Error()) +} + +func (e *StorageDriverError) Unwrap() error { return e.Err } + +func NewErrNoDeployedReleases(releaseName string) error { + return &StorageDriverError{ + ReleaseName: releaseName, + Err: ErrNoDeployedReleases, + } +} + +// Creator is the interface that wraps the Create method. +// +// Create stores the release or returns ErrReleaseExists +// if an identical release already exists. +type Creator interface { + Create(key string, rls release.Releaser) error +} + +// Updator is the interface that wraps the Update method. +// +// Update updates an existing release or returns +// ErrReleaseNotFound if the release does not exist. +type Updator interface { + Update(key string, rls release.Releaser) error +} + +// Deletor is the interface that wraps the Delete method. +// +// Delete deletes the release named by key or returns +// ErrReleaseNotFound if the release does not exist. +type Deletor interface { + Delete(key string) (release.Releaser, error) +} + +// Queryor is the interface that wraps the Get and List methods. +// +// Get returns the release named by key or returns ErrReleaseNotFound +// if the release does not exist. +// +// List returns the set of all releases that satisfy the filter predicate. +// +// Query returns the set of all releases that match the provided label set. +type Queryor interface { + Get(key string) (release.Releaser, error) + List(filter func(release.Releaser) bool) ([]release.Releaser, error) + Query(labels map[string]string) ([]release.Releaser, error) +} + +// Driver is the interface composed of Creator, Updator, Deletor, and Queryor +// interfaces. It defines the behavior for storing, updating, deleted, +// and retrieving Helm releases from some underlying storage mechanism, +// e.g. memory, configmaps. +type Driver interface { + Creator + Updator + Deletor + Queryor + Name() string +} + +// releaserToV1Release is a helper function to convert a v1 release passed by interface +// into the type object. +func releaserToV1Release(rel release.Releaser) (*rspb.Release, error) { + switch r := rel.(type) { + case rspb.Release: + return &r, nil + case *rspb.Release: + return r, nil + case nil: + return nil, nil + default: + return nil, fmt.Errorf("unsupported release type: %T", rel) + } +} diff --git a/internal/storage/driver/labels.go b/internal/storage/driver/labels.go new file mode 100644 index 000000000..be8262b81 --- /dev/null +++ b/internal/storage/driver/labels.go @@ -0,0 +1,48 @@ +/* +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 driver + +// labels is a map of key value pairs to be included as metadata in a configmap object. +type labels map[string]string + +func (lbs *labels) init() { *lbs = labels(make(map[string]string)) } +func (lbs labels) get(key string) string { return lbs[key] } +func (lbs labels) set(key, val string) { lbs[key] = val } + +func (lbs labels) keys() (ls []string) { + for key := range lbs { + ls = append(ls, key) + } + return ls +} + +func (lbs labels) match(set labels) bool { + for _, key := range set.keys() { + if lbs.get(key) != set.get(key) { + return false + } + } + return true +} + +func (lbs labels) toMap() map[string]string { return lbs } + +func (lbs *labels) fromMap(kvs map[string]string) { + for k, v := range kvs { + lbs.set(k, v) + } +} diff --git a/internal/storage/driver/labels_test.go b/internal/storage/driver/labels_test.go new file mode 100644 index 000000000..81c2c5149 --- /dev/null +++ b/internal/storage/driver/labels_test.go @@ -0,0 +1,55 @@ +/* +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 driver + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestLabelsMatch(t *testing.T) { + tests := []struct { + desc string + set1 labels + set2 labels + expect bool + }{ + { + "equal labels sets", + labels(map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}), + labels(map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}), + true, + }, + { + "disjoint label sets", + labels(map[string]string{"KEY_C": "VAL_C", "KEY_D": "VAL_D"}), + labels(map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}), + false, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + if tt.expect { + require.True(t, tt.set1.match(tt.set2)) + } else { + require.False(t, tt.set1.match(tt.set2)) + } + }) + } +} diff --git a/internal/storage/driver/memory.go b/internal/storage/driver/memory.go new file mode 100644 index 000000000..7ea4a014a --- /dev/null +++ b/internal/storage/driver/memory.go @@ -0,0 +1,255 @@ +/* +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 driver + +import ( + "log/slog" + "strconv" + "strings" + "sync" + + "helm.sh/helm/v4/internal/logging" + "helm.sh/helm/v4/pkg/release" +) + +var _ Driver = (*Memory)(nil) + +const ( + // MemoryDriverName is the string name of this driver. + MemoryDriverName = "Memory" + + defaultNamespace = "default" +) + +// A map of release names to list of release records +type memReleases map[string]records + +// Memory is the in-memory storage driver implementation. +type Memory struct { + sync.RWMutex + namespace string + // A map of namespaces to releases + cache map[string]memReleases + // Embed a LogHolder to provide logger functionality + logging.LogHolder +} + +// NewMemory initializes a new memory driver. +func NewMemory() *Memory { + m := &Memory{cache: map[string]memReleases{}, namespace: "default"} + m.SetLogger(slog.Default().Handler()) + return m +} + +// SetNamespace sets a specific namespace in which releases will be accessed. +// An empty string indicates all namespaces (for the list operation) +func (mem *Memory) SetNamespace(ns string) { + mem.namespace = ns +} + +// Name returns the name of the driver. +func (mem *Memory) Name() string { + return MemoryDriverName +} + +// Get returns the release named by key or returns ErrReleaseNotFound. +func (mem *Memory) Get(key string) (release.Releaser, error) { + defer unlock(mem.rlock()) + + keyWithoutPrefix := strings.TrimPrefix(key, "sh.helm.release.v1.") + switch elems := strings.Split(keyWithoutPrefix, ".v"); len(elems) { + case 2: + name, ver := elems[0], elems[1] + if _, err := strconv.Atoi(ver); err != nil { + return nil, ErrInvalidKey + } + if recs, ok := mem.cache[mem.namespace][name]; ok { + if r := recs.Get(key); r != nil { + return r.rls, nil + } + } + return nil, ErrReleaseNotFound + default: + return nil, ErrInvalidKey + } +} + +// List returns the list of all releases such that filter(release) == true +func (mem *Memory) List(filter func(release.Releaser) bool) ([]release.Releaser, error) { + defer unlock(mem.rlock()) + + var ls []release.Releaser + for namespace := range mem.cache { + if mem.namespace != "" { + // Should only list releases of this namespace + namespace = mem.namespace + } + for _, recs := range mem.cache[namespace] { + recs.Iter(func(_ int, rec *record) bool { + if filter(rec.rls) { + ls = append(ls, rec.rls) + } + return true + }) + } + if mem.namespace != "" { + // Should only list releases of this namespace + break + } + } + return ls, nil +} + +// Query returns the set of releases that match the provided set of labels +func (mem *Memory) Query(keyvals map[string]string) ([]release.Releaser, error) { + defer unlock(mem.rlock()) + + var lbs labels + + lbs.init() + lbs.fromMap(keyvals) + + var ls []release.Releaser + for namespace := range mem.cache { + if mem.namespace != "" { + // Should only query releases of this namespace + namespace = mem.namespace + } + for _, recs := range mem.cache[namespace] { + recs.Iter(func(_ int, rec *record) bool { + // A query for a release name that doesn't exist (has been deleted) + // can cause rec to be nil. + if rec == nil { + return false + } + if rec.lbs.match(lbs) { + ls = append(ls, rec.rls) + } + return true + }) + } + if mem.namespace != "" { + // Should only query releases of this namespace + break + } + } + + if len(ls) == 0 { + return nil, ErrReleaseNotFound + } + + return ls, nil +} + +// Create creates a new release or returns ErrReleaseExists. +func (mem *Memory) Create(key string, rel release.Releaser) error { + defer unlock(mem.wlock()) + + rls, err := releaserToV1Release(rel) + if err != nil { + return err + } + // For backwards compatibility, we protect against an unset namespace + namespace := rls.Namespace + if namespace == "" { + namespace = defaultNamespace + } + mem.SetNamespace(namespace) + + if _, ok := mem.cache[namespace]; !ok { + mem.cache[namespace] = memReleases{} + } + + if recs, ok := mem.cache[namespace][rls.Name]; ok { + if err := recs.Add(newRecord(key, rls)); err != nil { + return err + } + mem.cache[namespace][rls.Name] = recs + return nil + } + mem.cache[namespace][rls.Name] = records{newRecord(key, rls)} + return nil +} + +// Update updates a release or returns ErrReleaseNotFound. +func (mem *Memory) Update(key string, rel release.Releaser) error { + defer unlock(mem.wlock()) + + rls, err := releaserToV1Release(rel) + if err != nil { + return err + } + + // For backwards compatibility, we protect against an unset namespace + namespace := rls.Namespace + if namespace == "" { + namespace = defaultNamespace + } + mem.SetNamespace(namespace) + + if _, ok := mem.cache[namespace]; ok { + if rs, ok := mem.cache[namespace][rls.Name]; ok && rs.Exists(key) { + rs.Replace(key, newRecord(key, rls)) + return nil + } + } + return ErrReleaseNotFound +} + +// Delete deletes a release or returns ErrReleaseNotFound. +func (mem *Memory) Delete(key string) (release.Releaser, error) { + defer unlock(mem.wlock()) + + keyWithoutPrefix := strings.TrimPrefix(key, "sh.helm.release.v1.") + elems := strings.Split(keyWithoutPrefix, ".v") + + if len(elems) != 2 { + return nil, ErrInvalidKey + } + + name, ver := elems[0], elems[1] + if _, err := strconv.Atoi(ver); err != nil { + return nil, ErrInvalidKey + } + if _, ok := mem.cache[mem.namespace]; ok { + if recs, ok := mem.cache[mem.namespace][name]; ok { + if r := recs.Remove(key); r != nil { + // recs.Remove changes the slice reference, so we have to re-assign it. + mem.cache[mem.namespace][name] = recs + return r.rls, nil + } + } + } + return nil, ErrReleaseNotFound +} + +// wlock locks mem for writing +func (mem *Memory) wlock() func() { + mem.Lock() + return func() { mem.Unlock() } +} + +// rlock locks mem for reading +func (mem *Memory) rlock() func() { + mem.RLock() + return func() { mem.RUnlock() } +} + +// unlock calls fn which reverses a mem.rlock or mem.wlock. e.g: +// ```defer unlock(mem.rlock())```, locks mem for reading at the +// call point of defer and unlocks upon exiting the block. +func unlock(fn func()) { fn() } diff --git a/internal/storage/driver/memory_test.go b/internal/storage/driver/memory_test.go new file mode 100644 index 000000000..82f605e98 --- /dev/null +++ b/internal/storage/driver/memory_test.go @@ -0,0 +1,266 @@ +/* +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 driver + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + rspb "helm.sh/helm/v4/internal/release/v2" + "helm.sh/helm/v4/pkg/release" + "helm.sh/helm/v4/pkg/release/common" +) + +func TestMemoryName(t *testing.T) { + mem := NewMemory() + assert.Equalf(t, MemoryDriverName, mem.Name(), "Expected name to be %q, got %q", MemoryDriverName, mem.Name()) +} + +func TestMemoryCreate(t *testing.T) { + tests := []struct { + desc string + rls *rspb.Release + err bool + }{ + { + "create should succeed", + releaseStub("rls-c", 1, "default", common.StatusDeployed), + false, + }, + { + "create should fail (release already exists)", + releaseStub("rls-a", 1, "default", common.StatusDeployed), + true, + }, + { + "create in namespace should succeed", + releaseStub("rls-a", 1, "mynamespace", common.StatusDeployed), + false, + }, + { + "create in other namespace should fail (release already exists)", + releaseStub("rls-c", 1, "mynamespace", common.StatusDeployed), + true, + }, + } + + ts := tsFixtureMemory(t) + for _, tt := range tests { + key := testKey(tt.rls.Name, tt.rls.Version) + rls := tt.rls + err := ts.Create(key, rls) + + if tt.err { + require.Error(t, err, "Did not get expected error for %q\n", tt.desc) + } else { + require.NoError(t, err, "failed to create %q", tt.desc) + } + } +} + +func TestMemoryGet(t *testing.T) { + tests := []struct { + desc string + key string + namespace string + err bool + }{ + {"release key should exist", "rls-a.v1", "default", false}, + {"release key should not exist", "rls-a.v5", "default", true}, + {"release key in namespace should exist", "rls-c.v1", "mynamespace", false}, + {"release key in namespace should not exist", "rls-a.v1", "mynamespace", true}, + } + + ts := tsFixtureMemory(t) + for _, tt := range tests { + ts.SetNamespace(tt.namespace) + _, err := ts.Get(tt.key) + if tt.err { + require.Error(t, err, "Did not get expected error for %q '%s'\n", tt.desc, tt.key) + } else { + require.NoError(t, err, "Failed %q to get '%s'", tt.desc, tt.key) + } + } +} + +func TestMemoryList(t *testing.T) { + ts := tsFixtureMemory(t) + ts.SetNamespace("default") + + // list all deployed releases + dpl, err := ts.List(func(rel release.Releaser) bool { + rls := convertReleaserToV1(t, rel) + return rls.Info.Status == common.StatusDeployed + }) + // check + require.NoError(t, err, "Failed to list deployed releases") + assert.Len(t, dpl, 2, "Expected 2 deployed") + + // list all superseded releases + ssd, err := ts.List(func(rel release.Releaser) bool { + rls := convertReleaserToV1(t, rel) + return rls.Info.Status == common.StatusSuperseded + }) + // check + require.NoError(t, err, "Failed to list superseded releases") + assert.Len(t, ssd, 6, "Expected 6 superseded") + + // list all deleted releases + del, err := ts.List(func(rel release.Releaser) bool { + rls := convertReleaserToV1(t, rel) + return rls.Info.Status == common.StatusUninstalled + }) + // check + require.NoError(t, err, "Failed to list deleted releases") + assert.Empty(t, del, "Expected 0 deleted, got %d", len(del)) +} + +func TestMemoryQuery(t *testing.T) { + tests := []struct { + desc string + xlen int + namespace string + lbs map[string]string + }{ + { + "should be 2 query results", + 2, + "default", + map[string]string{"status": "deployed"}, + }, + { + "should be 1 query result", + 1, + "mynamespace", + map[string]string{"status": "deployed"}, + }, + } + + ts := tsFixtureMemory(t) + for _, tt := range tests { + ts.SetNamespace(tt.namespace) + l, err := ts.Query(tt.lbs) + require.NoError(t, err, "Failed to query") + + require.Equal(t, len(l), tt.xlen, "Expected %d results, actual %d\n", tt.xlen, len(l)) + } +} + +func TestMemoryUpdate(t *testing.T) { + tests := []struct { + desc string + key string + rls *rspb.Release + err bool + }{ + { + "update release status", + "rls-a.v4", + releaseStub("rls-a", 4, "default", common.StatusSuperseded), + false, + }, + { + "update release does not exist", + "rls-c.v1", + releaseStub("rls-c", 1, "default", common.StatusUninstalled), + true, + }, + { + "update release status in namespace", + "rls-c.v4", + releaseStub("rls-c", 4, "mynamespace", common.StatusSuperseded), + false, + }, + { + "update release in namespace does not exist", + "rls-a.v1", + releaseStub("rls-a", 1, "mynamespace", common.StatusUninstalled), + true, + }, + } + + ts := tsFixtureMemory(t) + for _, tt := range tests { + err := ts.Update(tt.key, tt.rls) + + if tt.err { + require.Error(t, err, "Did not get expected error for %q '%s'\n", tt.desc, tt.key) + } else { + require.NoError(t, err, "Failed %q", tt.desc) + + ts.SetNamespace(tt.rls.Namespace) + + r, err := ts.Get(tt.key) + require.NoError(t, err, "Failed to get") + require.Equalf(t, r, tt.rls, "Expected %v, actual %v\n", tt.rls, r) + } + } +} + +func TestMemoryDelete(t *testing.T) { + tests := []struct { + desc string + key string + namespace string + err bool + }{ + {"release key should exist", "rls-a.v4", "default", false}, + {"release key should not exist", "rls-a.v5", "default", true}, + {"release key from other namespace should not exist", "rls-c.v4", "default", true}, + {"release key from namespace should exist", "rls-c.v4", "mynamespace", false}, + {"release key from namespace should not exist", "rls-c.v5", "mynamespace", true}, + {"release key from namespace2 should not exist", "rls-a.v4", "mynamespace", true}, + } + + ts := tsFixtureMemory(t) + ts.SetNamespace("") + start, err := ts.Query(map[string]string{"status": "deployed"}) + require.NoError(t, err, "Query failed") + startLen := len(start) + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + ts.SetNamespace(tt.namespace) + + rel, err := ts.Delete(tt.key) + if tt.err { + require.Errorf(t, err, "Did not get expected error for %q '%s'\n", tt.desc, tt.key) + } else { + require.NoErrorf(t, err, "Failed %q to get '%s'", tt.desc, tt.key) + rls := convertReleaserToV1(t, rel) + require.Equalf(t, tt.key, fmt.Sprintf("%s.v%d", rls.Name, rls.Version), "Asked for delete on %s, but deleted %d", tt.key, rls.Version) + } + _, err = ts.Get(tt.key) + require.Error(t, err, "Expected an error when asking for a deleted key") + }) + } + + // Make sure that the deleted records are gone. + ts.SetNamespace("") + end, err := ts.Query(map[string]string{"status": "deployed"}) + require.NoError(t, err, "Query failed") + + if !assert.Len(t, end, startLen-2) { + for _, ee := range end { + rac, err := release.NewAccessor(ee) + require.NoError(t, err, "unable to get release accessor") + t.Logf("Name: %s, Version: %d", rac.Name(), rac.Version()) + } + } +} diff --git a/internal/storage/driver/mock_test.go b/internal/storage/driver/mock_test.go new file mode 100644 index 000000000..a5eff0fd1 --- /dev/null +++ b/internal/storage/driver/mock_test.go @@ -0,0 +1,267 @@ +/* +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 driver + +import ( + "context" + "fmt" + "testing" + + sqlmock "github.com/DATA-DOG/go-sqlmock" + sq "github.com/Masterminds/squirrel" + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/require" + + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kblabels "k8s.io/apimachinery/pkg/labels" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + + rspb "helm.sh/helm/v4/internal/release/v2" + "helm.sh/helm/v4/pkg/release/common" +) + +func releaseStub(name string, vers int, namespace string, status common.Status) *rspb.Release { + return &rspb.Release{ + Name: name, + Version: vers, + Namespace: namespace, + Info: &rspb.Info{Status: status}, + Labels: map[string]string{ + "key1": "val1", + "key2": "val2", + }, + } +} + +func testKey(name string, vers int) string { + return fmt.Sprintf("%s.v%d", name, vers) +} + +func tsFixtureMemory(t *testing.T) *Memory { + t.Helper() + hs := []*rspb.Release{ + // rls-a + releaseStub("rls-a", 4, "default", common.StatusDeployed), + releaseStub("rls-a", 1, "default", common.StatusSuperseded), + releaseStub("rls-a", 3, "default", common.StatusSuperseded), + releaseStub("rls-a", 2, "default", common.StatusSuperseded), + // rls-b + releaseStub("rls-b", 4, "default", common.StatusDeployed), + releaseStub("rls-b", 1, "default", common.StatusSuperseded), + releaseStub("rls-b", 3, "default", common.StatusSuperseded), + releaseStub("rls-b", 2, "default", common.StatusSuperseded), + // rls-c in other namespace + releaseStub("rls-c", 4, "mynamespace", common.StatusDeployed), + releaseStub("rls-c", 1, "mynamespace", common.StatusSuperseded), + releaseStub("rls-c", 3, "mynamespace", common.StatusSuperseded), + releaseStub("rls-c", 2, "mynamespace", common.StatusSuperseded), + } + + mem := NewMemory() + for _, tt := range hs { + require.NoError(t, mem.Create(testKey(tt.Name, tt.Version), tt), "Test setup failed to create") + } + return mem +} + +// newTestFixtureCfgMaps initializes a MockConfigMapsInterface. +// ConfigMaps are created for each release provided. +func newTestFixtureCfgMaps(t *testing.T, releases ...*rspb.Release) *ConfigMaps { + t.Helper() + var mock MockConfigMapsInterface + mock.Init(t, releases...) + + return NewConfigMaps(&mock) +} + +// MockConfigMapsInterface mocks a kubernetes ConfigMapsInterface +type MockConfigMapsInterface struct { + corev1.ConfigMapInterface + + objects map[string]*v1.ConfigMap +} + +// Init initializes the MockConfigMapsInterface with the set of releases. +func (mock *MockConfigMapsInterface) Init(t *testing.T, releases ...*rspb.Release) { + t.Helper() + mock.objects = map[string]*v1.ConfigMap{} + + for _, rls := range releases { + objkey := testKey(rls.Name, rls.Version) + + cfgmap, err := newConfigMapsObject(objkey, rls, nil) + require.NoError(t, err, "Failed to create configmap") + mock.objects[objkey] = cfgmap + } +} + +// Get returns the ConfigMap by name. +func (mock *MockConfigMapsInterface) Get(_ context.Context, name string, _ metav1.GetOptions) (*v1.ConfigMap, error) { + object, ok := mock.objects[name] + if !ok { + return nil, apierrors.NewNotFound(v1.Resource("tests"), name) + } + return object, nil +} + +// List returns all ConfigMaps. +func (mock *MockConfigMapsInterface) List(_ context.Context, opts metav1.ListOptions) (*v1.ConfigMapList, error) { + var list v1.ConfigMapList + + labelSelector, err := kblabels.Parse(opts.LabelSelector) + if err != nil { + return nil, err + } + + for _, cfgmap := range mock.objects { + if labelSelector.Matches(kblabels.Set(cfgmap.Labels)) { + list.Items = append(list.Items, *cfgmap) + } + } + return &list, nil +} + +// Create creates a new ConfigMap. +func (mock *MockConfigMapsInterface) Create(_ context.Context, cfgmap *v1.ConfigMap, _ metav1.CreateOptions) (*v1.ConfigMap, error) { + name := cfgmap.Name + if object, ok := mock.objects[name]; ok { + return object, apierrors.NewAlreadyExists(v1.Resource("tests"), name) + } + mock.objects[name] = cfgmap + return cfgmap, nil +} + +// Update updates a ConfigMap. +func (mock *MockConfigMapsInterface) Update(_ context.Context, cfgmap *v1.ConfigMap, _ metav1.UpdateOptions) (*v1.ConfigMap, error) { + name := cfgmap.Name + if _, ok := mock.objects[name]; !ok { + return nil, apierrors.NewNotFound(v1.Resource("tests"), name) + } + mock.objects[name] = cfgmap + return cfgmap, nil +} + +// Delete deletes a ConfigMap by name. +func (mock *MockConfigMapsInterface) Delete(_ context.Context, name string, _ metav1.DeleteOptions) error { + if _, ok := mock.objects[name]; !ok { + return apierrors.NewNotFound(v1.Resource("tests"), name) + } + delete(mock.objects, name) + return nil +} + +// newTestFixtureSecrets initializes a MockSecretsInterface. +// Secrets are created for each release provided. +func newTestFixtureSecrets(t *testing.T, releases ...*rspb.Release) *Secrets { + t.Helper() + var mock MockSecretsInterface + mock.Init(t, releases...) + + return NewSecrets(&mock) +} + +// MockSecretsInterface mocks a kubernetes SecretsInterface +type MockSecretsInterface struct { + corev1.SecretInterface + + objects map[string]*v1.Secret +} + +// Init initializes the MockSecretsInterface with the set of releases. +func (mock *MockSecretsInterface) Init(t *testing.T, releases ...*rspb.Release) { + t.Helper() + mock.objects = map[string]*v1.Secret{} + + for _, rls := range releases { + objkey := testKey(rls.Name, rls.Version) + + secret, err := newSecretsObject(objkey, rls, nil) + require.NoError(t, err, "Failed to create secret") + mock.objects[objkey] = secret + } +} + +// Get returns the Secret by name. +func (mock *MockSecretsInterface) Get(_ context.Context, name string, _ metav1.GetOptions) (*v1.Secret, error) { + object, ok := mock.objects[name] + if !ok { + return nil, apierrors.NewNotFound(v1.Resource("tests"), name) + } + return object, nil +} + +// List returns all Secrets. +func (mock *MockSecretsInterface) List(_ context.Context, opts metav1.ListOptions) (*v1.SecretList, error) { + var list v1.SecretList + + labelSelector, err := kblabels.Parse(opts.LabelSelector) + if err != nil { + return nil, err + } + + for _, secret := range mock.objects { + if labelSelector.Matches(kblabels.Set(secret.Labels)) { + list.Items = append(list.Items, *secret) + } + } + return &list, nil +} + +// Create creates a new Secret. +func (mock *MockSecretsInterface) Create(_ context.Context, secret *v1.Secret, _ metav1.CreateOptions) (*v1.Secret, error) { + name := secret.Name + if object, ok := mock.objects[name]; ok { + return object, apierrors.NewAlreadyExists(v1.Resource("tests"), name) + } + mock.objects[name] = secret + return secret, nil +} + +// Update updates a Secret. +func (mock *MockSecretsInterface) Update(_ context.Context, secret *v1.Secret, _ metav1.UpdateOptions) (*v1.Secret, error) { + name := secret.Name + if _, ok := mock.objects[name]; !ok { + return nil, apierrors.NewNotFound(v1.Resource("tests"), name) + } + mock.objects[name] = secret + return secret, nil +} + +// Delete deletes a Secret by name. +func (mock *MockSecretsInterface) Delete(_ context.Context, name string, _ metav1.DeleteOptions) error { + if _, ok := mock.objects[name]; !ok { + return apierrors.NewNotFound(v1.Resource("tests"), name) + } + delete(mock.objects, name) + return nil +} + +// newTestFixtureSQL mocks the SQL database (for testing purposes) +func newTestFixtureSQL(t *testing.T, _ ...*rspb.Release) (*SQL, sqlmock.Sqlmock) { + t.Helper() + sqlDB, mock, err := sqlmock.New() + require.NoError(t, err, "error when opening stub database connection") + + sqlxDB := sqlx.NewDb(sqlDB, "sqlmock") + return &SQL{ + db: sqlxDB, + namespace: "default", + statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), + }, mock +} diff --git a/internal/storage/driver/records.go b/internal/storage/driver/records.go new file mode 100644 index 000000000..14f13780d --- /dev/null +++ b/internal/storage/driver/records.go @@ -0,0 +1,127 @@ +/* +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 driver + +import ( + "sort" + "strconv" + + rspb "helm.sh/helm/v4/internal/release/v2" +) + +// records holds a list of in-memory release records +type records []*record + +func (rs records) Len() int { return len(rs) } +func (rs records) Swap(i, j int) { rs[i], rs[j] = rs[j], rs[i] } +func (rs records) Less(i, j int) bool { return rs[i].rls.Version < rs[j].rls.Version } + +func (rs *records) Add(r *record) error { + if r == nil { + return nil + } + + if rs.Exists(r.key) { + return ErrReleaseExists + } + + *rs = append(*rs, r) + sort.Sort(*rs) + + return nil +} + +func (rs records) Get(key string) *record { + if i, ok := rs.Index(key); ok { + return rs[i] + } + return nil +} + +func (rs *records) Iter(fn func(int, *record) bool) { + cp := make([]*record, len(*rs)) + copy(cp, *rs) + + for i, r := range cp { + if !fn(i, r) { + return + } + } +} + +func (rs *records) Index(key string) (int, bool) { + for i, r := range *rs { + if r.key == key { + return i, true + } + } + return -1, false +} + +func (rs records) Exists(key string) bool { + _, ok := rs.Index(key) + return ok +} + +func (rs *records) Remove(key string) (r *record) { + if i, ok := rs.Index(key); ok { + return rs.removeAt(i) + } + return nil +} + +func (rs *records) Replace(key string, rec *record) *record { + if i, ok := rs.Index(key); ok { + old := (*rs)[i] + (*rs)[i] = rec + return old + } + return nil +} + +func (rs *records) removeAt(index int) *record { + r := (*rs)[index] + (*rs)[index] = nil + copy((*rs)[index:], (*rs)[index+1:]) + *rs = (*rs)[:len(*rs)-1] + return r +} + +// record is the data structure used to cache releases +// for the in-memory storage driver +type record struct { + key string + lbs labels + rls *rspb.Release +} + +// newRecord creates a new in-memory release record +func newRecord(key string, rls *rspb.Release) *record { + var lbs labels + + lbs.init() + + // apply custom labels + lbs.fromMap(rls.Labels) + + lbs.set("name", rls.Name) + lbs.set("owner", "helm") + lbs.set("status", rls.Info.Status.String()) + lbs.set("version", strconv.Itoa(rls.Version)) + + return &record{key: key, lbs: lbs, rls: rls} +} diff --git a/internal/storage/driver/records_test.go b/internal/storage/driver/records_test.go new file mode 100644 index 000000000..049536971 --- /dev/null +++ b/internal/storage/driver/records_test.go @@ -0,0 +1,226 @@ +/* +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 driver + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "helm.sh/helm/v4/pkg/release/common" +) + +func TestRecordsAdd(t *testing.T) { + rs := records([]*record{ + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), + }) + + tests := []struct { + desc string + key string + ok bool + rec *record + }{ + { + "add valid key", + "rls-a.v3", + false, + newRecord("rls-a.v3", releaseStub("rls-a", 3, "default", common.StatusSuperseded)), + }, + { + "add already existing key", + "rls-a.v1", + true, + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusDeployed)), + }, + } + + for _, tt := range tests { + err := rs.Add(tt.rec) + if !tt.ok { + require.NoError(t, err, "failed: %q:", tt.desc) + } else { + require.Error(t, err) + } + } +} + +func TestRecordsRemove(t *testing.T) { + tests := []struct { + desc string + key string + ok bool + }{ + {"remove valid key", "rls-a.v1", false}, + {"remove invalid key", "rls-a.v", true}, + {"remove non-existent key", "rls-z.v1", true}, + } + + rs := records([]*record{ + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), + }) + + startLen := rs.Len() + + for _, tt := range tests { + r := rs.Remove(tt.key) + if tt.ok { + require.Nil(t, r, "Failed to %q (key = %s). Expected nil, got %v", tt.desc, tt.key, r) + } else { + require.NotNil(t, r) + } + } + + // We expect the total number of records will be less now than there were + // when we started. + endLen := rs.Len() + assert.Lessf(t, endLen, startLen, "expected ending length %d to be less than starting length %d", endLen, startLen) +} + +func TestRecordsRemoveAt(t *testing.T) { + rs := records([]*record{ + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), + }) + + require.Len(t, rs, 2, "Expected len=2 for mock") + + rs.Remove("rls-a.v1") + require.Len(t, rs, 1, "Expected length of rs to be 1, got %d", len(rs)) +} + +func TestRecordsGet(t *testing.T) { + rs := records([]*record{ + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), + }) + + tests := []struct { + desc string + key string + rec *record + }{ + { + "get valid key", + "rls-a.v1", + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)), + }, + { + "get invalid key", + "rls-a.v3", + nil, + }, + } + + for _, tt := range tests { + got := rs.Get(tt.key) + require.Equal(t, tt.rec, got, "Expected %v, got %v", tt.rec, got) + } +} + +func TestRecordsIndex(t *testing.T) { + rs := records([]*record{ + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), + }) + + tests := []struct { + desc string + key string + sort int + }{ + { + "get valid key", + "rls-a.v1", + 0, + }, + { + "get invalid key", + "rls-a.v3", + -1, + }, + } + + for _, tt := range tests { + got, _ := rs.Index(tt.key) + require.Equal(t, tt.sort, got, "Expected %d, got %d", tt.sort, got) + } +} + +func TestRecordsExists(t *testing.T) { + rs := records([]*record{ + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), + }) + + tests := []struct { + desc string + key string + ok bool + }{ + { + "get valid key", + "rls-a.v1", + true, + }, + { + "get invalid key", + "rls-a.v3", + false, + }, + } + + for _, tt := range tests { + got := rs.Exists(tt.key) + require.Equal(t, tt.ok, got, "Expected %t, got %t", tt.ok, got) + } +} + +func TestRecordsReplace(t *testing.T) { + rs := records([]*record{ + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", common.StatusSuperseded)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), + }) + + tests := []struct { + desc string + key string + rec *record + expected *record + }{ + { + "replace with existing key", + "rls-a.v2", + newRecord("rls-a.v3", releaseStub("rls-a", 3, "default", common.StatusSuperseded)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), + }, + { + "replace with non existing key", + "rls-a.v4", + newRecord("rls-a.v4", releaseStub("rls-a", 4, "default", common.StatusDeployed)), + nil, + }, + } + + for _, tt := range tests { + got := rs.Replace(tt.key, tt.rec) + require.Equalf(t, tt.expected, got, "Expected %v, got %v", tt.expected, got) + } +} diff --git a/internal/storage/driver/secrets.go b/internal/storage/driver/secrets.go new file mode 100644 index 000000000..e12aa2c1d --- /dev/null +++ b/internal/storage/driver/secrets.go @@ -0,0 +1,287 @@ +/* +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 driver + +import ( + "context" + "fmt" + "log/slog" + "strconv" + "strings" + "time" + + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kblabels "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/validation" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + + "helm.sh/helm/v4/internal/logging" + rspb "helm.sh/helm/v4/internal/release/v2" + "helm.sh/helm/v4/pkg/release" +) + +var _ Driver = (*Secrets)(nil) + +// SecretsDriverName is the string name of the driver. +const SecretsDriverName = "Secret" + +// Secrets is a wrapper around an implementation of a kubernetes +// SecretsInterface. +type Secrets struct { + impl corev1.SecretInterface + // Embed a LogHolder to provide logger functionality + logging.LogHolder +} + +// NewSecrets initializes a new Secrets wrapping an implementation of +// the kubernetes SecretsInterface. +func NewSecrets(impl corev1.SecretInterface) *Secrets { + s := &Secrets{ + impl: impl, + } + s.SetLogger(slog.Default().Handler()) + return s +} + +// Name returns the name of the driver. +func (secrets *Secrets) Name() string { + return SecretsDriverName +} + +// Get fetches the release named by key. The corresponding release is returned +// or error if not found. +func (secrets *Secrets) Get(key string) (release.Releaser, error) { + // fetch the secret holding the release named by key + obj, err := secrets.impl.Get(context.Background(), key, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + return nil, ErrReleaseNotFound + } + return nil, fmt.Errorf("get: failed to get %q: %w", key, err) + } + // found the secret, decode the base64 data string + r, err := decodeRelease(string(obj.Data["release"])) + if err != nil { + return r, fmt.Errorf("get: failed to decode data %q: %w", key, err) + } + r.Labels = filterSystemLabels(obj.Labels) + return r, nil +} + +// List fetches all releases and returns the list releases such +// that filter(release) == true. An error is returned if the +// secret fails to retrieve the releases. +func (secrets *Secrets) List(filter func(release.Releaser) bool) ([]release.Releaser, error) { + lsel := kblabels.Set{"owner": "helm"}.AsSelector() + opts := metav1.ListOptions{LabelSelector: lsel.String()} + + list, err := secrets.impl.List(context.Background(), opts) + if err != nil { + return nil, fmt.Errorf("list: failed to list: %w", err) + } + + var results []release.Releaser + + // iterate over the secrets object list + // and decode each release + for _, item := range list.Items { + rls, err := decodeRelease(string(item.Data["release"])) + if err != nil { + secrets.Logger().Debug( + "list failed to decode release", slog.String("key", item.Name), + slog.Any("error", err), + ) + continue + } + + rls.Labels = item.Labels + + if filter(rls) { + results = append(results, rls) + } + } + return results, nil +} + +// Query fetches all releases that match the provided map of labels. +// An error is returned if the secret fails to retrieve the releases. +func (secrets *Secrets) Query(labels map[string]string) ([]release.Releaser, error) { + ls := kblabels.Set{} + for k, v := range labels { + if errs := validation.IsValidLabelValue(v); len(errs) != 0 { + return nil, fmt.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; ")) + } + ls[k] = v + } + + opts := metav1.ListOptions{LabelSelector: ls.AsSelector().String()} + + list, err := secrets.impl.List(context.Background(), opts) + if err != nil { + return nil, fmt.Errorf("query: failed to query with labels: %w", err) + } + + if len(list.Items) == 0 { + return nil, ErrReleaseNotFound + } + + var results []release.Releaser + for _, item := range list.Items { + rls, err := decodeRelease(string(item.Data["release"])) + if err != nil { + secrets.Logger().Debug( + "failed to decode release", + slog.String("key", item.Name), + slog.Any("error", err), + ) + continue + } + rls.Labels = item.Labels + results = append(results, rls) + } + return results, nil +} + +// Create creates a new Secret holding the release. If the +// Secret already exists, ErrReleaseExists is returned. +func (secrets *Secrets) Create(key string, rel release.Releaser) error { + // set labels for secrets object meta data + var lbs labels + + rls, err := releaserToV1Release(rel) + if err != nil { + return err + } + + lbs.init() + lbs.fromMap(rls.Labels) + lbs.set("createdAt", strconv.FormatInt(time.Now().Unix(), 10)) + + // create a new secret to hold the release + obj, err := newSecretsObject(key, rls, lbs) + if err != nil { + return fmt.Errorf("create: failed to encode release %q: %w", rls.Name, err) + } + // push the secret object out into the kubiverse + if _, err := secrets.impl.Create(context.Background(), obj, metav1.CreateOptions{}); err != nil { + if apierrors.IsAlreadyExists(err) { + return ErrReleaseExists + } + + return fmt.Errorf("create: failed to create: %w", err) + } + return nil +} + +// Update updates the Secret holding the release. If not found +// the Secret is created to hold the release. +func (secrets *Secrets) Update(key string, rel release.Releaser) error { + // set labels for secrets object meta data + var lbs labels + + rls, err := releaserToV1Release(rel) + if err != nil { + return err + } + + lbs.init() + lbs.fromMap(rls.Labels) + lbs.set("modifiedAt", strconv.FormatInt(time.Now().Unix(), 10)) + + // create a new secret object to hold the release + obj, err := newSecretsObject(key, rls, lbs) + if err != nil { + return fmt.Errorf("update: failed to encode release %q: %w", rls.Name, err) + } + // push the secret object out into the kubiverse + _, err = secrets.impl.Update(context.Background(), obj, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("update: failed to update: %w", err) + } + return nil +} + +// Delete deletes the Secret holding the release named by key. +func (secrets *Secrets) Delete(key string) (rls release.Releaser, err error) { + // fetch the release to check existence + if rls, err = secrets.Get(key); err != nil { + return nil, err + } + // delete the release + err = secrets.impl.Delete(context.Background(), key, metav1.DeleteOptions{}) + if err != nil { + return nil, err + } + return rls, nil +} + +// newSecretsObject constructs a kubernetes Secret object +// to store a release. Each secret data entry is the base64 +// encoded gzipped string of a release. +// +// The following labels are used within each secret: +// +// "modifiedAt" - timestamp indicating when this secret was last modified. (set in Update) +// "createdAt" - timestamp indicating when this secret was created. (set in Create) +// "version" - version of the release. +// "status" - status of the release (see pkg/release/status.go for variants) +// "owner" - owner of the secret, currently "helm". +// "name" - name of the release. +func newSecretsObject(key string, rls *rspb.Release, lbs labels) (*v1.Secret, error) { + const owner = "helm" + + // encode the release + s, err := encodeRelease(rls) + if err != nil { + return nil, err + } + + if lbs == nil { + lbs.init() + } + + // apply custom labels + lbs.fromMap(rls.Labels) + + // apply labels + lbs.set("name", rls.Name) + lbs.set("owner", owner) + lbs.set("status", rls.Info.Status.String()) + lbs.set("version", strconv.Itoa(rls.Version)) + + // create and return secret object. + // Helm 3 introduced setting the 'Type' field + // in the Kubernetes storage object. + // Helm defines the field content as follows: + // /.v + // Type field for Chart v3: helm.sh/release.v2 + // Note: Version starts at 'v1' for Helm 3 and + // should be incremented if the release object + // metadata is modified. + // This would potentially be a breaking change + // and should only happen between major versions. + return &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: key, + Labels: lbs.toMap(), + }, + Type: "helm.sh/release.v2", + Data: map[string][]byte{"release": []byte(s)}, + }, nil +} diff --git a/internal/storage/driver/secrets_test.go b/internal/storage/driver/secrets_test.go new file mode 100644 index 000000000..4bdff656a --- /dev/null +++ b/internal/storage/driver/secrets_test.go @@ -0,0 +1,198 @@ +/* +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 driver + +import ( + "encoding/base64" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + + rspb "helm.sh/helm/v4/internal/release/v2" + "helm.sh/helm/v4/pkg/release" + "helm.sh/helm/v4/pkg/release/common" +) + +func TestSecretName(t *testing.T) { + c := newTestFixtureSecrets(t) + assert.Equal(t, SecretsDriverName, c.Name(), "Expected name to be %q, got %q", SecretsDriverName, c.Name()) +} + +func TestSecretGet(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...) + + // get release with key + got, err := secrets.Get(key) + require.NoError(t, err, "Failed to get release") + // compare fetched release with original + assert.Equalf(t, rel, got, "Expected {%v}, got {%v}", rel, got) +} + +func TestUNcompressedSecretGet(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + // Create a test fixture which contains an uncompressed release + secret, err := newSecretsObject(key, rel, nil) + require.NoError(t, err, "Failed to create secret") + b, err := json.Marshal(rel) + require.NoError(t, err, "Failed to marshal release") + secret.Data["release"] = []byte(base64.StdEncoding.EncodeToString(b)) + var mock MockSecretsInterface + mock.objects = map[string]*v1.Secret{key: secret} + secrets := NewSecrets(&mock) + + // get release with key + got, err := secrets.Get(key) + require.NoError(t, err, "Failed to get release") + // compare fetched release with original + assert.Equalf(t, rel, got, "Expected {%v}, got {%v}", rel, got) +} + +func TestSecretList(t *testing.T) { + secrets := newTestFixtureSecrets(t, []*rspb.Release{ + releaseStub("key-1", 1, "default", common.StatusUninstalled), + releaseStub("key-2", 1, "default", common.StatusUninstalled), + releaseStub("key-3", 1, "default", common.StatusDeployed), + releaseStub("key-4", 1, "default", common.StatusDeployed), + releaseStub("key-5", 1, "default", common.StatusSuperseded), + releaseStub("key-6", 1, "default", common.StatusSuperseded), + }...) + + // list all deleted releases + del, err := secrets.List(func(rel release.Releaser) bool { + rls := convertReleaserToV1(t, rel) + return rls.Info.Status == common.StatusUninstalled + }) + // check + require.NoError(t, err, "Failed to list deleted") + assert.Len(t, del, 2, "Expected 2 deleted") + + // list all deployed releases + dpl, err := secrets.List(func(rel release.Releaser) bool { + rls := convertReleaserToV1(t, rel) + return rls.Info.Status == common.StatusDeployed + }) + // check + require.NoError(t, err, "Failed to list deployed") + assert.Len(t, dpl, 2, "Expected 2 deployed") + + // list all superseded releases + ssd, err := secrets.List(func(rel release.Releaser) bool { + rls := convertReleaserToV1(t, rel) + return rls.Info.Status == common.StatusSuperseded + }) + // check + require.NoError(t, err, "Failed to list superseded") + require.Len(t, ssd, 2, "Expected 2 superseded") + // Check if release having both system and custom labels, this is needed to ensure that selector filtering would work. + rls := convertReleaserToV1(t, ssd[0]) + require.Contains(t, rls.Labels, "name", "Expected 'name' label in results, actual %v", rls.Labels) + require.Contains(t, rls.Labels, "key1", "Expected 'key1' label in results, actual %v", rls.Labels) +} + +func TestSecretQuery(t *testing.T) { + secrets := newTestFixtureSecrets(t, []*rspb.Release{ + releaseStub("key-1", 1, "default", common.StatusUninstalled), + releaseStub("key-2", 1, "default", common.StatusUninstalled), + releaseStub("key-3", 1, "default", common.StatusDeployed), + releaseStub("key-4", 1, "default", common.StatusDeployed), + releaseStub("key-5", 1, "default", common.StatusSuperseded), + releaseStub("key-6", 1, "default", common.StatusSuperseded), + }...) + + rls, err := secrets.Query(map[string]string{"status": "deployed"}) + require.NoError(t, err, "Failed to query") + require.Len(t, rls, 2, "Expected 2 results, actual %d", len(rls)) + + _, err = secrets.Query(map[string]string{"name": "notExist"}) + assert.ErrorIs(t, err, ErrReleaseNotFound) +} + +func TestSecretCreate(t *testing.T) { + secrets := newTestFixtureSecrets(t) + + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + // store the release in a secret + require.NoErrorf(t, secrets.Create(key, rel), "Failed to create release with key %q", key) + + // get the release back + got, err := secrets.Get(key) + require.NoError(t, err, "Failed to get release with key %q", key) + + // compare created release with original + assert.Equalf(t, rel, got, "Expected {%v}, got {%v}", rel, got) +} + +func TestSecretUpdate(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...) + + // modify release status code + rel.Info.Status = common.StatusSuperseded + + // perform the update + require.NoErrorf(t, secrets.Update(key, rel), "Failed to update release") + + // fetch the updated release + goti, err := secrets.Get(key) + require.NoError(t, err, "Failed to get release with key %q", key) + got := convertReleaserToV1(t, goti) + + // check release has actually been updated by comparing modified fields + assert.Equal(t, got.Info.Status, rel.Info.Status, "Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String()) +} + +func TestSecretDelete(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...) + + // perform the delete on a non-existing release + _, err := secrets.Delete("nonexistent") + require.ErrorIs(t, err, ErrReleaseNotFound, "Expected ErrReleaseNotFound") + + // perform the delete + rls, err := secrets.Delete(key) + require.NoError(t, err, "Failed to delete release with key %q", key) + assert.Equalf(t, rel, rls, "Expected {%v}, got {%v}", rel, rls) + _, err = secrets.Get(key) + assert.ErrorIs(t, err, ErrReleaseNotFound) +} diff --git a/internal/storage/driver/sql.go b/internal/storage/driver/sql.go new file mode 100644 index 000000000..653507ba9 --- /dev/null +++ b/internal/storage/driver/sql.go @@ -0,0 +1,742 @@ +/* +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 driver + +import ( + "fmt" + "log/slog" + "maps" + "sort" + "strconv" + "time" + + "github.com/jmoiron/sqlx" + migrate "github.com/rubenv/sql-migrate" + + sq "github.com/Masterminds/squirrel" + + // Import pq for postgres dialect + _ "github.com/lib/pq" + + "helm.sh/helm/v4/internal/logging" + rspb "helm.sh/helm/v4/internal/release/v2" + "helm.sh/helm/v4/pkg/release" +) + +var _ Driver = (*SQL)(nil) + +var labelMap = map[string]struct{}{ + "modifiedAt": {}, + "createdAt": {}, + "version": {}, + "status": {}, + "owner": {}, + "name": {}, +} + +const postgreSQLDialect = "postgres" + +// SQLDriverName is the string name of this driver. +const SQLDriverName = "SQL" + +const ( + sqlReleaseTableName = "releases_v1" + sqlCustomLabelsTableName = "custom_labels_v1" +) + +const ( + sqlReleaseTableKeyColumn = "key" + sqlReleaseTableTypeColumn = "type" + sqlReleaseTableBodyColumn = "body" + sqlReleaseTableNameColumn = "name" + sqlReleaseTableNamespaceColumn = "namespace" + sqlReleaseTableVersionColumn = "version" + sqlReleaseTableStatusColumn = "status" + sqlReleaseTableOwnerColumn = "owner" + sqlReleaseTableCreatedAtColumn = "createdAt" + sqlReleaseTableModifiedAtColumn = "modifiedAt" + + sqlCustomLabelsTableReleaseKeyColumn = "releaseKey" + sqlCustomLabelsTableReleaseNamespaceColumn = "releaseNamespace" + sqlCustomLabelsTableKeyColumn = "key" + sqlCustomLabelsTableValueColumn = "value" +) + +// Following limits based on k8s labels limits - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set +const ( + sqlCustomLabelsTableKeyMaxLength = 253 + 1 + 63 + sqlCustomLabelsTableValueMaxLength = 63 +) + +const ( + sqlReleaseDefaultOwner = "helm" + sqlReleaseDefaultType = "helm.sh/release.v2" +) + +// SQL is the sql storage driver implementation. +type SQL struct { + db *sqlx.DB + namespace string + statementBuilder sq.StatementBuilderType + // Embed a LogHolder to provide logger functionality + logging.LogHolder +} + +// Name returns the name of the driver. +func (s *SQL) Name() string { + return SQLDriverName +} + +// Check if all migrations al +func (s *SQL) checkAlreadyApplied(migrations []*migrate.Migration) bool { + // make map (set) of ids for fast search + migrationsIDs := make(map[string]struct{}) + for _, migration := range migrations { + migrationsIDs[migration.Id] = struct{}{} + } + + // get list of applied migrations + migrate.SetDisableCreateTable(true) + records, err := migrate.GetMigrationRecords(s.db.DB, postgreSQLDialect) + migrate.SetDisableCreateTable(false) + if err != nil { + s.Logger().Debug("failed to get migration records", slog.Any("error", err)) + return false + } + + for _, record := range records { + if _, ok := migrationsIDs[record.Id]; ok { + s.Logger().Debug("found previous migration", "id", record.Id, "appliedAt", record.AppliedAt) + delete(migrationsIDs, record.Id) + } + } + + // check if all migrations applied + if len(migrationsIDs) != 0 { + for id := range migrationsIDs { + s.Logger().Debug("find unapplied migration", "id", id) + } + return false + } + return true +} + +func (s *SQL) ensureDBSetup() error { + migrations := &migrate.MemoryMigrationSource{ + Migrations: []*migrate.Migration{ + { + Id: "init", + Up: []string{ + fmt.Sprintf(` + CREATE TABLE %s ( + %s VARCHAR(90), + %s VARCHAR(64) NOT NULL, + %s TEXT NOT NULL, + %s VARCHAR(64) NOT NULL, + %s VARCHAR(64) NOT NULL, + %s INTEGER NOT NULL, + %s TEXT NOT NULL, + %s TEXT NOT NULL, + %s INTEGER NOT NULL, + %s INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY(%s, %s) + ); + CREATE INDEX ON %s (%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; + + ALTER TABLE %s ENABLE ROW LEVEL SECURITY; + `, + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableTypeColumn, + sqlReleaseTableBodyColumn, + sqlReleaseTableNameColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableVersionColumn, + sqlReleaseTableStatusColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableCreatedAtColumn, + sqlReleaseTableModifiedAtColumn, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableName, + sqlReleaseTableVersionColumn, + sqlReleaseTableName, + sqlReleaseTableStatusColumn, + sqlReleaseTableName, + sqlReleaseTableOwnerColumn, + sqlReleaseTableName, + sqlReleaseTableCreatedAtColumn, + sqlReleaseTableName, + sqlReleaseTableModifiedAtColumn, + sqlReleaseTableName, + sqlReleaseTableName, + ), + }, + Down: []string{ + fmt.Sprintf(` + DROP TABLE %s; + `, sqlReleaseTableName), + }, + }, + { + Id: "custom_labels", + Up: []string{ + fmt.Sprintf(` + CREATE TABLE %s ( + %s VARCHAR(64), + %s VARCHAR(67), + %s VARCHAR(%d), + %s VARCHAR(%d) + ); + CREATE INDEX ON %s (%s, %s); + + GRANT ALL ON %s TO PUBLIC; + ALTER TABLE %s ENABLE ROW LEVEL SECURITY; + `, + sqlCustomLabelsTableName, + sqlCustomLabelsTableReleaseKeyColumn, + sqlCustomLabelsTableReleaseNamespaceColumn, + sqlCustomLabelsTableKeyColumn, + sqlCustomLabelsTableKeyMaxLength, + sqlCustomLabelsTableValueColumn, + sqlCustomLabelsTableValueMaxLength, + sqlCustomLabelsTableName, + sqlCustomLabelsTableReleaseKeyColumn, + sqlCustomLabelsTableReleaseNamespaceColumn, + sqlCustomLabelsTableName, + sqlCustomLabelsTableName, + ), + }, + Down: []string{ + fmt.Sprintf(` + DELETE TABLE %s; + `, sqlCustomLabelsTableName), + }, + }, + }, + } + + // Check that init migration already applied + if s.checkAlreadyApplied(migrations.Migrations) { + return nil + } + + // Populate the database with the relations we need if they don't exist yet + _, err := migrate.Exec(s.db.DB, postgreSQLDialect, migrations, migrate.Up) + return err +} + +// SQLReleaseWrapper describes how Helm releases are stored in an SQL database +type SQLReleaseWrapper struct { + // The primary key, made of {release-name}.{release-version} + Key string `db:"key"` + + // See https://github.com/helm/helm/blob/c9fe3d118caec699eb2565df9838673af379ce12/pkg/storage/driver/secrets.go#L231 + Type string `db:"type"` + + // The rspb.Release body, as a base64-encoded string + Body string `db:"body"` + + // Release "labels" that can be used as filters in the storage.Query(labels map[string]string) + // we implemented. Note that allowing Helm users to filter against new dimensions will require a + // new migration to be added, and the Create and/or update functions to be updated accordingly. + Name string `db:"name"` + Namespace string `db:"namespace"` + Version int `db:"version"` + Status string `db:"status"` + Owner string `db:"owner"` + CreatedAt int `db:"createdAt"` + ModifiedAt int `db:"modifiedAt"` +} + +type SQLReleaseCustomLabelWrapper struct { + ReleaseKey string `db:"release_key"` + ReleaseNamespace string `db:"release_namespace"` + Key string `db:"key"` + Value string `db:"value"` +} + +// NewSQL initializes a new sql driver. +func NewSQL(connectionString, namespace string) (*SQL, error) { + db, err := sqlx.Connect(postgreSQLDialect, connectionString) + if err != nil { + return nil, err + } + + driver := &SQL{ + db: db, + statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), + } + + if err := driver.ensureDBSetup(); err != nil { + return nil, err + } + + driver.namespace = namespace + driver.SetLogger(slog.Default().Handler()) + + return driver, nil +} + +// Get returns the release named by key. +func (s *SQL) Get(key string) (release.Releaser, error) { + var record SQLReleaseWrapper + + qb := s.statementBuilder. + Select(sqlReleaseTableBodyColumn). + From(sqlReleaseTableName). + Where(sq.Eq{sqlReleaseTableKeyColumn: key}). + Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}) + + query, args, err := qb.ToSql() + if err != nil { + s.Logger().Debug("failed to build query", slog.Any("error", err)) + return nil, err + } + + // Get will return an error if the result is empty + if err := s.db.Get(&record, query, args...); err != nil { + s.Logger().Debug("got SQL error when getting release", slog.String("key", key), slog.Any("error", err)) + return nil, ErrReleaseNotFound + } + + release, err := decodeRelease(record.Body) + if err != nil { + s.Logger().Debug("failed to decode data", slog.String("key", key), slog.Any("error", err)) + return nil, err + } + + if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil { + s.Logger().Debug( + "failed to get release custom labels", + slog.String("namespace", s.namespace), + slog.String("key", key), + slog.Any("error", err), + ) + return nil, err + } + + return release, nil +} + +// List returns the list of all releases such that filter(release) == true +func (s *SQL) List(filter func(release.Releaser) bool) ([]release.Releaser, error) { + sb := s.statementBuilder. + Select(sqlReleaseTableKeyColumn, sqlReleaseTableNamespaceColumn, sqlReleaseTableBodyColumn). + From(sqlReleaseTableName). + Where(sq.Eq{sqlReleaseTableOwnerColumn: sqlReleaseDefaultOwner}) + + // If a namespace was specified, we only list releases from that namespace + if s.namespace != "" { + sb = sb.Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}) + } + + query, args, err := sb.ToSql() + if err != nil { + s.Logger().Debug("failed to build query", slog.Any("error", err)) + return nil, err + } + + records := []SQLReleaseWrapper{} + if err := s.db.Select(&records, query, args...); err != nil { + s.Logger().Debug("failed to list", slog.Any("error", err)) + return nil, err + } + + var releases []release.Releaser + for _, record := range records { + release, err := decodeRelease(record.Body) + if err != nil { + s.Logger().Debug("failed to decode release", slog.Any("record", record), slog.Any("error", err)) + continue + } + + if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil { + s.Logger().Debug( + "failed to get release custom labels", + slog.String("namespace", record.Namespace), + slog.String("key", record.Key), + slog.Any("error", err), + ) + return nil, err + } + maps.Copy(release.Labels, getReleaseSystemLabels(release)) + + if filter(release) { + releases = append(releases, release) + } + } + + return releases, nil +} + +// Query returns the set of releases that match the provided set of labels. +func (s *SQL) Query(labels map[string]string) ([]release.Releaser, error) { + sb := s.statementBuilder. + Select(sqlReleaseTableKeyColumn, sqlReleaseTableNamespaceColumn, sqlReleaseTableBodyColumn). + From(sqlReleaseTableName) + + keys := make([]string, 0, len(labels)) + for key := range labels { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + _, ok := labelMap[key] + if !ok { + s.Logger().Debug("unknown label", "key", key) + return nil, fmt.Errorf("unknown label %s", key) + } + sb = sb.Where(sq.Eq{key: labels[key]}) + } + + // If a namespace was specified, we only list releases from that namespace + if s.namespace != "" { + sb = sb.Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}) + } + + // Build our query + query, args, err := sb.ToSql() + if err != nil { + s.Logger().Debug("failed to build query", slog.Any("error", err)) + return nil, err + } + + records := []SQLReleaseWrapper{} + if err := s.db.Select(&records, query, args...); err != nil { + s.Logger().Debug("failed to query with labels", slog.Any("error", err)) + return nil, err + } + + if len(records) == 0 { + return nil, ErrReleaseNotFound + } + + var releases []release.Releaser + for _, record := range records { + release, err := decodeRelease(record.Body) + if err != nil { + s.Logger().Debug("failed to decode release", slog.Any("record", record), slog.Any("error", err)) + continue + } + + if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil { + s.Logger().Debug( + "failed to get release custom labels", + slog.String("namespace", record.Namespace), + slog.String("key", record.Key), + slog.Any("error", err), + ) + return nil, err + } + + releases = append(releases, release) + } + + if len(releases) == 0 { + return nil, ErrReleaseNotFound + } + + return releases, nil +} + +// Create creates a new release. +func (s *SQL) Create(key string, rel release.Releaser) error { + rls, err := releaserToV1Release(rel) + if err != nil { + return err + } + + namespace := rls.Namespace + if namespace == "" { + namespace = defaultNamespace + } + s.namespace = namespace + + body, err := encodeRelease(rls) + if err != nil { + s.Logger().Debug("failed to encode release", slog.Any("error", err)) + return err + } + + transaction, err := s.db.Beginx() + if err != nil { + s.Logger().Debug("failed to start SQL transaction", slog.Any("error", err)) + return fmt.Errorf("error beginning transaction: %w", err) + } + + insertQuery, args, err := s.statementBuilder. + Insert(sqlReleaseTableName). + Columns( + sqlReleaseTableKeyColumn, + sqlReleaseTableTypeColumn, + sqlReleaseTableBodyColumn, + sqlReleaseTableNameColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableVersionColumn, + sqlReleaseTableStatusColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableCreatedAtColumn, + ). + Values( + key, + sqlReleaseDefaultType, + body, + rls.Name, + namespace, + int(rls.Version), + rls.Info.Status.String(), + sqlReleaseDefaultOwner, + int(time.Now().Unix()), + ).ToSql() + if err != nil { + s.Logger().Debug("failed to build insert query", slog.Any("error", err)) + return err + } + + if _, err := transaction.Exec(insertQuery, args...); err != nil { + defer transaction.Rollback() + + selectQuery, args, buildErr := s.statementBuilder. + Select(sqlReleaseTableKeyColumn). + From(sqlReleaseTableName). + Where(sq.Eq{sqlReleaseTableKeyColumn: key}). + Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). + ToSql() + if buildErr != nil { + s.Logger().Debug("failed to build select query", "error", buildErr) + return err + } + + var record SQLReleaseWrapper + if err := transaction.Get(&record, selectQuery, args...); err == nil { + s.Logger().Debug("release already exists", slog.String("key", key)) + return ErrReleaseExists + } + + s.Logger().Debug("failed to store release in SQL database", slog.String("key", key), slog.Any("error", err)) + return err + } + + // Filtering labels before insert cause in SQL storage driver system releases are stored in separate columns of release table + for k, v := range filterSystemLabels(rls.Labels) { + insertLabelsQuery, args, err := s.statementBuilder. + Insert(sqlCustomLabelsTableName). + Columns( + sqlCustomLabelsTableReleaseKeyColumn, + sqlCustomLabelsTableReleaseNamespaceColumn, + sqlCustomLabelsTableKeyColumn, + sqlCustomLabelsTableValueColumn, + ). + Values( + key, + namespace, + k, + v, + ).ToSql() + if err != nil { + defer transaction.Rollback() + s.Logger().Debug("failed to build insert query", slog.Any("error", err)) + return err + } + + if _, err := transaction.Exec(insertLabelsQuery, args...); err != nil { + defer transaction.Rollback() + s.Logger().Debug("failed to write Labels", slog.Any("error", err)) + return err + } + } + defer transaction.Commit() + + return nil +} + +// Update updates a release. +// +// Custom labels on an existing revision are meant to be preserved. A release's +// labels are set by Create, and a revision keeps the labels it was created with +// once it is superseded, rather than picking up the labels of the upgrade that +// superseded it. TestUpgradeRelease_Labels in pkg/action is what asserts this. +// +// The drivers arrive at that from opposite directions. Here labels live in a +// separate table written only by Create, so an update leaves them untouched. The +// configmaps, memory and secrets drivers instead store labels on the record +// itself and replace the record wholesale on update, so they have to re-apply +// the labels every time or the update would discard all of them. A side effect +// is that those drivers persist a label change where this driver silently would +// not. No caller changes labels between Create and Update, so the two agree in +// practice. +func (s *SQL) Update(key string, rel release.Releaser) error { + rls, err := releaserToV1Release(rel) + if err != nil { + return err + } + namespace := rls.Namespace + if namespace == "" { + namespace = defaultNamespace + } + s.namespace = namespace + + body, err := encodeRelease(rls) + if err != nil { + s.Logger().Debug("failed to encode release", slog.Any("error", err)) + return err + } + + query, args, err := s.statementBuilder. + Update(sqlReleaseTableName). + Set(sqlReleaseTableBodyColumn, body). + Set(sqlReleaseTableNameColumn, rls.Name). + Set(sqlReleaseTableVersionColumn, int(rls.Version)). + Set(sqlReleaseTableStatusColumn, rls.Info.Status.String()). + Set(sqlReleaseTableOwnerColumn, sqlReleaseDefaultOwner). + Set(sqlReleaseTableTypeColumn, sqlReleaseDefaultType). + Set(sqlReleaseTableModifiedAtColumn, int(time.Now().Unix())). + Where(sq.Eq{sqlReleaseTableKeyColumn: key}). + Where(sq.Eq{sqlReleaseTableNamespaceColumn: namespace}). + ToSql() + if err != nil { + s.Logger().Debug("failed to build update query", slog.Any("error", err)) + return err + } + + if _, err := s.db.Exec(query, args...); err != nil { + s.Logger().Debug("failed to update release in SQL database", slog.String("key", key), slog.Any("error", err)) + return err + } + + return nil +} + +// Delete deletes a release or returns ErrReleaseNotFound. +func (s *SQL) Delete(key string) (release.Releaser, error) { + transaction, err := s.db.Beginx() + if err != nil { + s.Logger().Debug("failed to start SQL transaction", slog.Any("error", err)) + return nil, fmt.Errorf("error beginning transaction: %w", err) + } + + selectQuery, args, err := s.statementBuilder. + Select(sqlReleaseTableBodyColumn). + From(sqlReleaseTableName). + Where(sq.Eq{sqlReleaseTableKeyColumn: key}). + Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). + ToSql() + if err != nil { + s.Logger().Debug("failed to build select query", slog.Any("error", err)) + return nil, err + } + + var record SQLReleaseWrapper + err = transaction.Get(&record, selectQuery, args...) + if err != nil { + s.Logger().Debug("release not found", slog.String("key", key), slog.Any("error", err)) + return nil, ErrReleaseNotFound + } + + release, err := decodeRelease(record.Body) + if err != nil { + s.Logger().Debug("failed to decode release", slog.String("key", key), slog.Any("error", err)) + transaction.Rollback() + return nil, err + } + defer transaction.Commit() + + deleteQuery, args, err := s.statementBuilder. + Delete(sqlReleaseTableName). + Where(sq.Eq{sqlReleaseTableKeyColumn: key}). + Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). + ToSql() + if err != nil { + s.Logger().Debug("failed to build delete query", slog.Any("error", err)) + return nil, err + } + + _, err = transaction.Exec(deleteQuery, args...) + if err != nil { + s.Logger().Debug("failed perform delete query", slog.Any("error", err)) + return release, err + } + + if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil { + s.Logger().Debug( + "failed to get release custom labels", + slog.String("namespace", s.namespace), + slog.String("key", key), + slog.Any("error", err)) + return nil, err + } + + deleteCustomLabelsQuery, args, err := s.statementBuilder. + Delete(sqlCustomLabelsTableName). + Where(sq.Eq{sqlCustomLabelsTableReleaseKeyColumn: key}). + Where(sq.Eq{sqlCustomLabelsTableReleaseNamespaceColumn: s.namespace}). + ToSql() + if err != nil { + s.Logger().Debug("failed to build delete Labels query", slog.Any("error", err)) + return nil, err + } + _, err = transaction.Exec(deleteCustomLabelsQuery, args...) + return release, err +} + +// Get release custom labels from database +func (s *SQL) getReleaseCustomLabels(key, _ string) (map[string]string, error) { + query, args, err := s.statementBuilder. + Select(sqlCustomLabelsTableKeyColumn, sqlCustomLabelsTableValueColumn). + From(sqlCustomLabelsTableName). + Where(sq.Eq{ + sqlCustomLabelsTableReleaseKeyColumn: key, + sqlCustomLabelsTableReleaseNamespaceColumn: s.namespace, + }). + ToSql() + if err != nil { + return nil, err + } + + labelsList := []SQLReleaseCustomLabelWrapper{} + if err := s.db.Select(&labelsList, query, args...); err != nil { + return nil, err + } + + labelsMap := make(map[string]string) + for _, i := range labelsList { + labelsMap[i.Key] = i.Value + } + + return filterSystemLabels(labelsMap), nil +} + +// Rebuild system labels from release object +func getReleaseSystemLabels(rls *rspb.Release) map[string]string { + return map[string]string{ + "name": rls.Name, + "owner": sqlReleaseDefaultOwner, + "status": rls.Info.Status.String(), + "version": strconv.Itoa(rls.Version), + } +} diff --git a/internal/storage/driver/sql_test.go b/internal/storage/driver/sql_test.go new file mode 100644 index 000000000..99c323680 --- /dev/null +++ b/internal/storage/driver/sql_test.go @@ -0,0 +1,564 @@ +/* +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 driver + +import ( + "database/sql/driver" + "errors" + "fmt" + "reflect" + "regexp" + "testing" + "time" + + sqlmock "github.com/DATA-DOG/go-sqlmock" + migrate "github.com/rubenv/sql-migrate" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + rspb "helm.sh/helm/v4/internal/release/v2" + "helm.sh/helm/v4/pkg/release" + "helm.sh/helm/v4/pkg/release/common" +) + +const recentTimestampTolerance = time.Second + +func recentUnixTimestamp() sqlmock.Argument { + return recentUnixTimestampArgument{} +} + +type recentUnixTimestampArgument struct{} + +func (recentUnixTimestampArgument) Match(value driver.Value) bool { + var ts int64 + switch v := value.(type) { + case int: + ts = int64(v) + case int64: + ts = v + default: + return false + } + + diff := time.Since(time.Unix(ts, 0)) + if diff < 0 { + diff = -diff + } + + return diff <= recentTimestampTolerance +} + +func TestSQLName(t *testing.T) { + sqlDriver, _ := newTestFixtureSQL(t) + assert.Equal(t, SQLDriverName, sqlDriver.Name(), "Expected name to be %s, got %s", SQLDriverName, sqlDriver.Name()) +} + +func TestSQLGet(t *testing.T) { + vers := int(1) + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + body, _ := encodeRelease(rel) + + sqlDriver, mock := newTestFixtureSQL(t) + + query := fmt.Sprintf( + regexp.QuoteMeta("SELECT %s FROM %s WHERE %s = $1 AND %s = $2"), + sqlReleaseTableBodyColumn, + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + ) + + mock. + ExpectQuery(query). + WithArgs(key, namespace). + WillReturnRows( + mock.NewRows([]string{ + sqlReleaseTableBodyColumn, + }).AddRow( + body, + ), + ).RowsWillBeClosed() + + mockGetReleaseCustomLabels(mock, key, namespace, rel.Labels) + + got, err := sqlDriver.Get(key) + require.NoError(t, err, "Failed to get release") + + assert.Equalf(t, rel, got, "Expected release {%v}, got {%v}", rel, got) + assert.NoErrorf(t, mock.ExpectationsWereMet(), "sql expectations weren't met") +} + +func TestSQLList(t *testing.T) { + releases := []*rspb.Release{} + releases = append(releases, + releaseStub("key-1", 1, "default", common.StatusUninstalled), + releaseStub("key-2", 1, "default", common.StatusUninstalled), + releaseStub("key-3", 1, "default", common.StatusDeployed), + releaseStub("key-4", 1, "default", common.StatusDeployed), + releaseStub("key-5", 1, "default", common.StatusSuperseded), + releaseStub("key-6", 1, "default", common.StatusSuperseded), + ) + + sqlDriver, mock := newTestFixtureSQL(t) + + for range 3 { + query := fmt.Sprintf( + "SELECT %s, %s, %s FROM %s WHERE %s = $1 AND %s = $2", + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableBodyColumn, + sqlReleaseTableName, + sqlReleaseTableOwnerColumn, + sqlReleaseTableNamespaceColumn, + ) + + rows := mock.NewRows([]string{ + sqlReleaseTableBodyColumn, + }) + for _, r := range releases { + body, _ := encodeRelease(r) + rows.AddRow(body) + } + mock. + ExpectQuery(regexp.QuoteMeta(query)). + WithArgs(sqlReleaseDefaultOwner, sqlDriver.namespace). + WillReturnRows(rows).RowsWillBeClosed() + + for _, r := range releases { + mockGetReleaseCustomLabels(mock, "", r.Namespace, r.Labels) + } + } + + // list all deleted releases + del, err := sqlDriver.List(func(rel release.Releaser) bool { + rls := convertReleaserToV1(t, rel) + return rls.Info.Status == common.StatusUninstalled + }) + // check + require.NoError(t, err, "Failed to list deleted") + assert.Len(t, del, 2, "Expected 2 deleted") + + // list all deployed releases + dpl, err := sqlDriver.List(func(rel release.Releaser) bool { + rls := convertReleaserToV1(t, rel) + return rls.Info.Status == common.StatusDeployed + }) + // check + require.NoError(t, err, "Failed to list deployed") + assert.Len(t, dpl, 2, "Expected 2 deployed") + + // list all superseded releases + ssd, err := sqlDriver.List(func(rel release.Releaser) bool { + rls := convertReleaserToV1(t, rel) + return rls.Info.Status == common.StatusSuperseded + }) + // check + require.NoError(t, err, "Failed to list superseded") + require.Len(t, ssd, 2, "Expected 2 superseded") + require.NoErrorf(t, mock.ExpectationsWereMet(), "sql expectations weren't met") + + // Check if release having both system and custom labels, this is needed to ensure that selector filtering would work. + rls := convertReleaserToV1(t, ssd[0]) + require.Contains(t, rls.Labels, "name", "Expected 'name' label in results, actual %v", rls.Labels) + require.Contains(t, rls.Labels, "key1", "Expected 'key1' label in results, actual %v", rls.Labels) +} + +func TestSqlCreate(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + sqlDriver, mock := newTestFixtureSQL(t) + body, _ := encodeRelease(rel) + + query := fmt.Sprintf( + "INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)", + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableTypeColumn, + sqlReleaseTableBodyColumn, + sqlReleaseTableNameColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableVersionColumn, + sqlReleaseTableStatusColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableCreatedAtColumn, + ) + + mock.ExpectBegin() + mock. + ExpectExec(regexp.QuoteMeta(query)). + WithArgs(key, sqlReleaseDefaultType, body, rel.Name, rel.Namespace, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, recentUnixTimestamp()). + WillReturnResult(sqlmock.NewResult(1, 1)) + + labelsQuery := fmt.Sprintf( + "INSERT INTO %s (%s,%s,%s,%s) VALUES ($1,$2,$3,$4)", + sqlCustomLabelsTableName, + sqlCustomLabelsTableReleaseKeyColumn, + sqlCustomLabelsTableReleaseNamespaceColumn, + sqlCustomLabelsTableKeyColumn, + sqlCustomLabelsTableValueColumn, + ) + + mock.MatchExpectationsInOrder(false) + for k, v := range filterSystemLabels(rel.Labels) { + mock. + ExpectExec(regexp.QuoteMeta(labelsQuery)). + WithArgs(key, rel.Namespace, k, v). + WillReturnResult(sqlmock.NewResult(1, 1)) + } + mock.ExpectCommit() + + require.NoErrorf(t, sqlDriver.Create(key, rel), "failed to create release with key %s", key) + assert.NoErrorf(t, mock.ExpectationsWereMet(), "sql expectations weren't met") +} + +func TestSqlCreateAlreadyExists(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + sqlDriver, mock := newTestFixtureSQL(t) + body, _ := encodeRelease(rel) + + insertQuery := fmt.Sprintf( + "INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)", + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableTypeColumn, + sqlReleaseTableBodyColumn, + sqlReleaseTableNameColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableVersionColumn, + sqlReleaseTableStatusColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableCreatedAtColumn, + ) + + // Insert fails (primary key already exists) + mock.ExpectBegin() + mock. + ExpectExec(regexp.QuoteMeta(insertQuery)). + WithArgs(key, sqlReleaseDefaultType, body, rel.Name, rel.Namespace, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, recentUnixTimestamp()). + WillReturnError(errors.New("dialect dependent SQL error")) + + selectQuery := fmt.Sprintf( + regexp.QuoteMeta("SELECT %s FROM %s WHERE %s = $1 AND %s = $2"), + sqlReleaseTableKeyColumn, + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + ) + + // Let's check that we do make sure the error is due to a release already existing + mock. + ExpectQuery(selectQuery). + WithArgs(key, namespace). + WillReturnRows( + mock.NewRows([]string{ + sqlReleaseTableKeyColumn, + }).AddRow( + key, + ), + ).RowsWillBeClosed() + mock.ExpectRollback() + + require.Errorf(t, sqlDriver.Create(key, rel), "failed to create release with key %s", key) + assert.NoErrorf(t, mock.ExpectationsWereMet(), "sql expectations weren't met") +} + +func TestSqlUpdate(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + sqlDriver, mock := newTestFixtureSQL(t) + body, _ := encodeRelease(rel) + + query := fmt.Sprintf( + "UPDATE %s SET %s = $1, %s = $2, %s = $3, %s = $4, %s = $5, %s = $6, %s = $7 WHERE %s = $8 AND %s = $9", + sqlReleaseTableName, + sqlReleaseTableBodyColumn, + sqlReleaseTableNameColumn, + sqlReleaseTableVersionColumn, + sqlReleaseTableStatusColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableTypeColumn, + sqlReleaseTableModifiedAtColumn, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + ) + + mock. + ExpectExec(regexp.QuoteMeta(query)). + WithArgs(body, rel.Name, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, sqlReleaseDefaultType, recentUnixTimestamp(), key, namespace). + WillReturnResult(sqlmock.NewResult(0, 1)) + + require.NoErrorf(t, sqlDriver.Update(key, rel), "failed to update release with key %s", key) + assert.NoErrorf(t, mock.ExpectationsWereMet(), "sql expectations weren't met") +} + +func TestSqlQuery(t *testing.T) { + // Reflect actual use cases in ../storage.go + labelSetUnknown := map[string]string{ + "name": "smug-pigeon", + "owner": sqlReleaseDefaultOwner, + "status": "unknown", + } + labelSetDeployed := map[string]string{ + "name": "smug-pigeon", + "owner": sqlReleaseDefaultOwner, + "status": "deployed", + } + labelSetAll := map[string]string{ + "name": "smug-pigeon", + "owner": sqlReleaseDefaultOwner, + } + + supersededRelease := releaseStub("smug-pigeon", 1, "default", common.StatusSuperseded) + supersededReleaseBody, _ := encodeRelease(supersededRelease) + deployedRelease := releaseStub("smug-pigeon", 2, "default", common.StatusDeployed) + deployedReleaseBody, _ := encodeRelease(deployedRelease) + + // Let's actually start our test + sqlDriver, mock := newTestFixtureSQL(t) + + query := fmt.Sprintf( + "SELECT %s, %s, %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3 AND %s = $4", + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableBodyColumn, + sqlReleaseTableName, + sqlReleaseTableNameColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableStatusColumn, + sqlReleaseTableNamespaceColumn, + ) + + mock. + ExpectQuery(regexp.QuoteMeta(query)). + WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "unknown", "default"). + WillReturnRows( + mock.NewRows([]string{ + sqlReleaseTableBodyColumn, + }), + ).RowsWillBeClosed() + + mock. + ExpectQuery(regexp.QuoteMeta(query)). + WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "deployed", "default"). + WillReturnRows( + mock.NewRows([]string{ + sqlReleaseTableBodyColumn, + }).AddRow( + deployedReleaseBody, + ), + ).RowsWillBeClosed() + + mockGetReleaseCustomLabels(mock, "", deployedRelease.Namespace, deployedRelease.Labels) + + query = fmt.Sprintf( + "SELECT %s, %s, %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3", + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + sqlReleaseTableBodyColumn, + sqlReleaseTableName, + sqlReleaseTableNameColumn, + sqlReleaseTableOwnerColumn, + sqlReleaseTableNamespaceColumn, + ) + + mock. + ExpectQuery(regexp.QuoteMeta(query)). + WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "default"). + WillReturnRows( + mock.NewRows([]string{ + sqlReleaseTableBodyColumn, + }).AddRow( + supersededReleaseBody, + ).AddRow( + deployedReleaseBody, + ), + ).RowsWillBeClosed() + + mockGetReleaseCustomLabels(mock, "", supersededRelease.Namespace, supersededRelease.Labels) + mockGetReleaseCustomLabels(mock, "", deployedRelease.Namespace, deployedRelease.Labels) + + _, err := sqlDriver.Query(labelSetUnknown) + require.Errorf(t, err, "Expected error {%v}, got nil", ErrReleaseNotFound) + require.ErrorIsf(t, err, ErrReleaseNotFound, "failed to query for unknown smug-pigeon release") + + results, err := sqlDriver.Query(labelSetDeployed) + require.NoError(t, err, "failed to query for deployed smug-pigeon release") + + for _, res := range results { + assert.Equalf(t, res, deployedRelease, "Expected release {%v}, got {%v}", deployedRelease, res) + } + + results, err = sqlDriver.Query(labelSetAll) + require.NoError(t, err, "failed to query release history for smug-pigeon") + assert.Len(t, results, 2, "expected a resultset of size 2") + + for _, res := range results { + assert.False(t, !reflect.DeepEqual(res, deployedRelease) && !reflect.DeepEqual(res, supersededRelease), "Expected release {%v} or {%v}, got {%v}", deployedRelease, supersededRelease, res) + } + + assert.NoErrorf(t, mock.ExpectationsWereMet(), "sql expectations weren't met") +} + +func TestSqlDelete(t *testing.T) { + vers := 1 + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, common.StatusDeployed) + + body, _ := encodeRelease(rel) + + sqlDriver, mock := newTestFixtureSQL(t) + + selectQuery := fmt.Sprintf( + "SELECT %s FROM %s WHERE %s = $1 AND %s = $2", + sqlReleaseTableBodyColumn, + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + ) + + mock.ExpectBegin() + mock. + ExpectQuery(regexp.QuoteMeta(selectQuery)). + WithArgs(key, namespace). + WillReturnRows( + mock.NewRows([]string{ + sqlReleaseTableBodyColumn, + }).AddRow( + body, + ), + ).RowsWillBeClosed() + + deleteQuery := fmt.Sprintf( + "DELETE FROM %s WHERE %s = $1 AND %s = $2", + sqlReleaseTableName, + sqlReleaseTableKeyColumn, + sqlReleaseTableNamespaceColumn, + ) + + mock. + ExpectExec(regexp.QuoteMeta(deleteQuery)). + WithArgs(key, namespace). + WillReturnResult(sqlmock.NewResult(0, 1)) + + mockGetReleaseCustomLabels(mock, key, namespace, rel.Labels) + + deleteLabelsQuery := fmt.Sprintf( + "DELETE FROM %s WHERE %s = $1 AND %s = $2", + sqlCustomLabelsTableName, + sqlCustomLabelsTableReleaseKeyColumn, + sqlCustomLabelsTableReleaseNamespaceColumn, + ) + mock. + ExpectExec(regexp.QuoteMeta(deleteLabelsQuery)). + WithArgs(key, namespace). + WillReturnResult(sqlmock.NewResult(0, 1)) + + mock.ExpectCommit() + + deletedRelease, err := sqlDriver.Delete(key) + require.NoError(t, err) + err = mock.ExpectationsWereMet() + assert.NoErrorf(t, err, "sql expectations weren't met") + require.NoError(t, err, "failed to delete release with key %q", key) + + assert.Equalf(t, rel, deletedRelease, "Expected release {%v}, got {%v}", rel, deletedRelease) +} + +func mockGetReleaseCustomLabels(mock sqlmock.Sqlmock, key, namespace string, labels map[string]string) { + query := fmt.Sprintf( + regexp.QuoteMeta("SELECT %s, %s FROM %s WHERE %s = $1 AND %s = $2"), + sqlCustomLabelsTableKeyColumn, + sqlCustomLabelsTableValueColumn, + sqlCustomLabelsTableName, + sqlCustomLabelsTableReleaseKeyColumn, + sqlCustomLabelsTableReleaseNamespaceColumn, + ) + + eq := mock.ExpectQuery(query). + WithArgs(key, namespace) + + returnRows := mock.NewRows([]string{ + sqlCustomLabelsTableKeyColumn, + sqlCustomLabelsTableValueColumn, + }) + for k, v := range labels { + returnRows.AddRow(k, v) + } + eq.WillReturnRows(returnRows).RowsWillBeClosed() +} + +func TestSqlCheckAppliedMigrations(t *testing.T) { + cases := []struct { + migrationsToApply []*migrate.Migration + appliedMigrationsIDs []string + expectedResult bool + errorExplanation string + }{ + { + migrationsToApply: []*migrate.Migration{{Id: "init1"}, {Id: "init2"}, {Id: "init3"}}, + appliedMigrationsIDs: []string{"1", "2", "init1", "3", "init2", "4", "5"}, + expectedResult: false, + errorExplanation: "Has found one migration id \"init3\" as applied, that was not applied", + }, + { + migrationsToApply: []*migrate.Migration{{Id: "init1"}, {Id: "init2"}, {Id: "init3"}}, + appliedMigrationsIDs: []string{"1", "2", "init1", "3", "init2", "4", "init3", "5"}, + expectedResult: true, + errorExplanation: "Has not found one or more migration ids, that was applied", + }, + { + migrationsToApply: []*migrate.Migration{{Id: "init"}}, + appliedMigrationsIDs: []string{"1", "2", "3", "inits", "4", "tinit", "5"}, + expectedResult: false, + errorExplanation: "Has found single \"init\", that was not applied", + }, + { + migrationsToApply: []*migrate.Migration{{Id: "init"}}, + appliedMigrationsIDs: []string{"1", "2", "init", "3", "init2", "4", "init3", "5"}, + expectedResult: true, + errorExplanation: "Has not found single migration id \"init\", that was applied", + }, + } + for i, c := range cases { + sqlDriver, mock := newTestFixtureSQL(t) + rows := sqlmock.NewRows([]string{"id", "applied_at"}) + for _, id := range c.appliedMigrationsIDs { + rows.AddRow(id, time.Time{}) + } + mock. + ExpectQuery(""). + WillReturnRows(rows) + mock.ExpectCommit() + assert.Equal(t, c.expectedResult, sqlDriver.checkAlreadyApplied(c.migrationsToApply), "Test case: %v, Expected: %v, Have: %v, Explanation: %v", i, c.expectedResult, !c.expectedResult, c.errorExplanation) + } +} diff --git a/internal/storage/driver/util.go b/internal/storage/driver/util.go new file mode 100644 index 000000000..10cf1adf1 --- /dev/null +++ b/internal/storage/driver/util.go @@ -0,0 +1,131 @@ +/* +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 driver + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "encoding/json" + "io" + "slices" + + rspb "helm.sh/helm/v4/internal/release/v2" +) + +var b64 = base64.StdEncoding + +var magicGzip = []byte{0x1f, 0x8b, 0x08} + +// releaseVersionLabel carries the release object schema version on storage +// objects that have no native field for it. Secrets record it in their Type +// field, but ConfigMaps have no equivalent, so the marker is a label instead. +// Absence of the label means release v1, so records written before this label +// existed continue to read correctly. +const releaseVersionLabel = "helm.sh/release-version" + +// releaseVersion is the schema version written by this package. +const releaseVersion = "v2" + +var systemLabels = []string{"name", "owner", "status", "version", "createdAt", "modifiedAt", releaseVersionLabel} + +// encodeRelease encodes a release returning a base64 encoded +// gzipped string representation, or error. +func encodeRelease(rls *rspb.Release) (string, error) { + b, err := json.Marshal(rls) + if err != nil { + return "", err + } + var buf bytes.Buffer + w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) + if err != nil { + return "", err + } + if _, err = w.Write(b); err != nil { + w.Close() + return "", err + } + if err := w.Close(); err != nil { + return "", err + } + + return b64.EncodeToString(buf.Bytes()), nil +} + +// decodeRelease decodes the bytes of data into a release +// type. Data must contain a base64 encoded gzipped string of a +// valid release, otherwise an error is returned. +func decodeRelease(data string) (*rspb.Release, error) { + // base64 decode string + b, err := b64.DecodeString(data) + if err != nil { + return nil, err + } + + // For backwards compatibility with releases that were stored before + // compression was introduced we skip decompression if the + // gzip magic header is not found + if len(b) > 3 && bytes.Equal(b[0:3], magicGzip) { + r, err := gzip.NewReader(bytes.NewReader(b)) + if err != nil { + return nil, err + } + defer r.Close() + b2, err := io.ReadAll(r) + if err != nil { + return nil, err + } + b = b2 + } + + var rls rspb.Release + // unmarshal release object bytes + if err := json.Unmarshal(b, &rls); err != nil { + return nil, err + } + return &rls, nil +} + +// Checks if label is system +func isSystemLabel(key string) bool { + return slices.Contains(GetSystemLabels(), key) +} + +// Removes system labels from labels map +func filterSystemLabels(lbs map[string]string) map[string]string { + result := make(map[string]string) + for k, v := range lbs { + if !isSystemLabel(k) { + result[k] = v + } + } + return result +} + +// Checks if labels array contains system labels +func ContainsSystemLabels(lbs map[string]string) bool { + for k := range lbs { + if isSystemLabel(k) { + return true + } + } + return false +} + +func GetSystemLabels() []string { + return systemLabels +} diff --git a/internal/storage/driver/util_test.go b/internal/storage/driver/util_test.go new file mode 100644 index 000000000..f84f6a67a --- /dev/null +++ b/internal/storage/driver/util_test.go @@ -0,0 +1,105 @@ +/* +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 driver + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetSystemLabel(t *testing.T) { + output := GetSystemLabels() + assert.Equalf(t, systemLabels, output, "Expected {%v}, got {%v}", systemLabels, output) +} + +func TestIsSystemLabel(t *testing.T) { + tests := map[string]bool{ + "name": true, + "owner": true, + "test": false, + "NaMe": false, + } + for label, result := range tests { + output := isSystemLabel(label) + assert.Equalf(t, output, result, "Output %t not equal to expected %t", output, result) + } +} + +func TestFilterSystemLabels(t *testing.T) { + tests := [][2]map[string]string{ + {nil, map[string]string{}}, + {map[string]string{}, map[string]string{}}, + {map[string]string{ + "name": "name", + "owner": "owner", + "status": "status", + "version": "version", + "createdAt": "createdAt", + "modifiedAt": "modifiedAt", + }, map[string]string{}}, + {map[string]string{ + "StaTus": "status", + "name": "name", + "owner": "owner", + "key": "value", + }, map[string]string{ + "StaTus": "status", + "key": "value", + }}, + {map[string]string{ + "key1": "value1", + "key2": "value2", + }, map[string]string{ + "key1": "value1", + "key2": "value2", + }}, + } + for _, test := range tests { + output := filterSystemLabels(test[0]) + assert.Equalf(t, test[1], output, "Expected {%v}, got {%v}", test[1], output) + } +} + +func TestContainsSystemLabels(t *testing.T) { + tests := []struct { + input map[string]string + output bool + }{ + {nil, false}, + {map[string]string{}, false}, + {map[string]string{ + "name": "name", + "owner": "owner", + "status": "status", + "version": "version", + "createdAt": "createdAt", + "modifiedAt": "modifiedAt", + }, true}, + {map[string]string{ + "StaTus": "status", + "name": "name", + "owner": "owner", + "key": "value", + }, true}, + {map[string]string{ + "key1": "value1", + "key2": "value2", + }, false}, + } + for _, test := range tests { + output := ContainsSystemLabels(test.input) + assert.Equalf(t, test.output, output, "Expected {%v}, got {%v}", test.output, output) + } +} diff --git a/pkg/storage/driver/records.go b/pkg/storage/driver/records.go index f78b76b8b..3393bb603 100644 --- a/pkg/storage/driver/records.go +++ b/pkg/storage/driver/records.go @@ -114,6 +114,10 @@ func newRecord(key string, rls *rspb.Release) *record { var lbs labels lbs.init() + + // apply custom labels + lbs.fromMap(rls.Labels) + lbs.set("name", rls.Name) lbs.set("owner", "helm") lbs.set("status", rls.Info.Status.String()) diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go index 6602f2ee3..2b278f7cb 100644 --- a/pkg/storage/driver/sql.go +++ b/pkg/storage/driver/sql.go @@ -576,6 +576,20 @@ func (s *SQL) Create(key string, rel release.Releaser) error { } // Update updates a release. +// +// Custom labels on an existing revision are meant to be preserved. A release's +// labels are set by Create, and a revision keeps the labels it was created with +// once it is superseded, rather than picking up the labels of the upgrade that +// superseded it. TestUpgradeRelease_Labels in pkg/action is what asserts this. +// +// The drivers arrive at that from opposite directions. Here labels live in a +// separate table written only by Create, so an update leaves them untouched. The +// configmaps, memory and secrets drivers instead store labels on the record +// itself and replace the record wholesale on update, so they have to re-apply +// the labels every time or the update would discard all of them. A side effect +// is that those drivers persist a label change where this driver silently would +// not. No caller changes labels between Create and Update, so the two agree in +// practice. func (s *SQL) Update(key string, rel release.Releaser) error { rls, err := releaserToV1Release(rel) if err != nil { diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 1d7fb9103..04e863e2c 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -30,11 +30,23 @@ import ( "helm.sh/helm/v4/pkg/storage/driver" ) -// HelmStorageType is the type field of the Kubernetes storage object which stores the Helm release -// version. It is modified slightly replacing the '/': sh.helm/release.v1 -// Note: The version 'v1' is incremented if the release object metadata is -// modified between major releases. -// This constant is used as a prefix for the Kubernetes storage object name. +// HelmStorageType is the prefix used for the name of the Kubernetes storage object +// that holds a release. It is derived from that object's 'Type' field +// (helm.sh/release.v1) by reversing the domain and replacing the '/' with a '.'. +// +// The prefix is deliberately decoupled from the 'Type' field +// (helm.sh/release.v1, helm.sh/release.v2). Type records the schema +// of the encoded release body, so it tracks the release object version. This +// prefix only keeps release names unique, and was added to stop Helm 3 records +// colliding with Helm 2 ones (https://github.com/helm/helm/issues/6435). +// +// The prefix is therefore not incremented with the release object version. +// Kubernetes objects cannot be renamed in place, so doing so would mean +// recreating every record and deleting the old one, per revision, per namespace. +// Until that completed, Get, Update and Delete would all miss, since they address +// objects by exact key, while List, Query and History would keep returning the old +// records because they select on labels. The only thing gained is seeing the +// release object version in the object name, which Type already records. const HelmStorageType = "sh.helm.release.v1" // Storage represents a storage engine for a Release. From 4c8a1b2361d7fc64e05c3290a67f0611ff9f7a99 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Mon, 21 Sep 2026 06:50:46 -0600 Subject: [PATCH 570/571] chore(deps): bump otlploghttp and stdoutlog to v0.21.0 otel/log v0.21.0 removed log.Kind, log.Value and log.KeyValue, so the otlploghttp and stdoutlog exporters must move to the matching release or they fail to compile. Signed-off-by: Terry Howe --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 04607a453..5fb3f0b8e 100644 --- a/go.mod +++ b/go.mod @@ -148,14 +148,14 @@ require ( go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect go.opentelemetry.io/otel v1.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.21.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect go.opentelemetry.io/otel/log v0.21.0 // indirect diff --git a/go.sum b/go.sum index 588486e68..af29c67aa 100644 --- a/go.sum +++ b/go.sum @@ -356,8 +356,8 @@ go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0 h1:WseeVYf5dJZTsyPiyW5L14k5qsSibqXAMTSiFEDiWr0= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0/go.mod h1:SiLZnQS6Qk2eCpvr2CH/XMAOa64TWGXxEZJZCpD2Lmc= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0/go.mod h1:ji9vId85hMxqfvICA0Jt8JqEdrXaAkcpkI9HPXya0ro= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.21.0 h1:fvNHGyo3CdRv/DQveXqhqBxnKTDyRaC5sMSQxilX/A0= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.21.0/go.mod h1:zyGrjRKL2B/6+Jc/m4/otPoZqV2MY9ZjC/aBraRO7zc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY= @@ -370,8 +370,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 h1:QBajQ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0/go.mod h1:08ZQLjrPLQ6R4kAXvuOvODEer5Yh4CoFvll5qB2BCI8= go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs= go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.21.0 h1:2lpf4hnrasYIsUyEXwnTZq5lsxrMm4T2Bwb06IctAZQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.21.0/go.mod h1:YWOW6h7jwApz9Pl76ie/izUsSPj0s2MdIlpqbPqaf3U= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPfuTmWVkEu8J8qXaQwuV30pjCcAUvP8= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw= From d31cd6992fc7858922de6ebe5984a06fd6aa0b80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 05:57:15 -0600 Subject: [PATCH 571/571] chore(deps): bump the github-actions group with 4 updates (#32677) Bumps the github-actions group with 4 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action). Updates `github/codeql-action/init` from 4.38.0 to 4.38.1 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/b96794f015dfd88f77b49b1c93e0fa7110f94c63...1c5b675653bb5c22dbe9b12b556ec555138e09fd) Updates `github/codeql-action/autobuild` from 4.38.0 to 4.38.1 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/b96794f015dfd88f77b49b1c93e0fa7110f94c63...1c5b675653bb5c22dbe9b12b556ec555138e09fd) Updates `github/codeql-action/analyze` from 4.38.0 to 4.38.1 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/b96794f015dfd88f77b49b1c93e0fa7110f94c63...1c5b675653bb5c22dbe9b12b556ec555138e09fd) Updates `github/codeql-action/upload-sarif` from 4.38.0 to 4.38.1 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/b96794f015dfd88f77b49b1c93e0fa7110f94c63...1c5b675653bb5c22dbe9b12b556ec555138e09fd) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.38.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/autobuild dependency-version: 4.38.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/analyze dependency-version: 4.38.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.38.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecards.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c20fa58a8..cff58f8a9 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # pinv4.38.0 + uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # pinv4.38.1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # pinv4.38.0 + uses: github/codeql-action/autobuild@1c5b675653bb5c22dbe9b12b556ec555138e09fd # pinv4.38.1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # pinv4.38.0 + uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # pinv4.38.1 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 09e0e8030..4749d5fce 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 + uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1 with: sarif_file: results.sarif