From 351bb78ee5a22cb0e68818a310d77d7240edc0b1 Mon Sep 17 00:00:00 2001 From: Artem Vdovin Date: Thu, 12 Jun 2025 23:33:53 +0500 Subject: [PATCH 001/114] fix index concurrency Signed-off-by: Artem Vdovin --- pkg/repo/chartrepo.go | 36 ++++++++++++++++++++++++++++++++++-- pkg/repo/index.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index e41226fa4..bbcab2ae8 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -17,6 +17,7 @@ limitations under the License. package repo // import "helm.sh/helm/v4/pkg/repo" import ( + "context" "crypto/rand" "encoding/base64" "encoding/json" @@ -27,6 +28,9 @@ import ( "os" "path/filepath" "strings" + "time" + + "github.com/gofrs/flock" "helm.sh/helm/v4/pkg/getter" "helm.sh/helm/v4/pkg/helmpath" @@ -106,13 +110,41 @@ func (r *ChartRepository) DownloadIndexFile() (string, error) { for name := range indexFile.Entries { fmt.Fprintln(&charts, name) } + chartsFile := filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)) os.MkdirAll(filepath.Dir(chartsFile), 0755) - os.WriteFile(chartsFile, []byte(charts.String()), 0644) - // Create the index file in the cache directory fname := filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name)) os.MkdirAll(filepath.Dir(fname), 0755) + + // context for lock files + lockCtx, cancel := context.WithTimeout(context.Background(), 90*time.Second) + defer cancel() + + // lock the charts file + chLock := flock.New(filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)+".lock")) + chLocked, err := chLock.TryLockContext(lockCtx, 500*time.Millisecond) + if err == nil && chLocked { + defer chLock.Unlock() + } + if err != nil { + return "", err + } + + // write charts files after lock + os.WriteFile(chartsFile, []byte(charts.String()), 0644) + + // lock the index file + idxLock := flock.New(filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name)+".lock")) + idxLocked, err := idxLock.TryLockContext(lockCtx, 500*time.Millisecond) + if err == nil && idxLocked { + defer idxLock.Unlock() + } + if err != nil { + return "", err + } + + // Create the index file in the cache directory return fname, os.WriteFile(fname, index, 0644) } diff --git a/pkg/repo/index.go b/pkg/repo/index.go index c26d7581c..b93816d71 100644 --- a/pkg/repo/index.go +++ b/pkg/repo/index.go @@ -18,6 +18,7 @@ package repo import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -30,6 +31,7 @@ import ( "time" "github.com/Masterminds/semver/v3" + "github.com/gofrs/flock" "sigs.k8s.io/yaml" "helm.sh/helm/v4/internal/fileutil" @@ -103,6 +105,19 @@ func NewIndexFile() *IndexFile { // LoadIndexFile takes a file at the given path and returns an IndexFile object func LoadIndexFile(path string) (*IndexFile, error) { + lockCtx, cancel := context.WithTimeout(context.Background(), 90*time.Second) + defer cancel() + + idxLock := flock.New(filepath.Join(path + ".lock")) + + idxLocked, err := idxLock.TryRLockContext(lockCtx, 500*time.Millisecond) + if err == nil && idxLocked { + defer idxLock.Unlock() + } + if err != nil { + return nil, err + } + b, err := os.ReadFile(path) if err != nil { return nil, err @@ -225,6 +240,19 @@ func (i IndexFile) Get(name, version string) (*ChartVersion, error) { // // The mode on the file is set to 'mode'. func (i IndexFile) WriteFile(dest string, mode os.FileMode) error { + lockCtx, cancel := context.WithTimeout(context.Background(), 90*time.Second) + defer cancel() + + idxLock := flock.New(dest + ".lock") + idxLocked, err := idxLock.TryLockContext(lockCtx, 500*time.Millisecond) + + if err == nil && idxLocked { + defer idxLock.Unlock() + } + if err != nil { + return err + } + b, err := yaml.Marshal(i) if err != nil { return err @@ -237,6 +265,19 @@ func (i IndexFile) WriteFile(dest string, mode os.FileMode) error { // // The mode on the file is set to 'mode'. func (i IndexFile) WriteJSONFile(dest string, mode os.FileMode) error { + lockCtx, cancel := context.WithTimeout(context.Background(), 90*time.Second) + defer cancel() + + idxLock := flock.New(dest + ".lock") + idxLocked, err := idxLock.TryLockContext(lockCtx, 500*time.Millisecond) + + if err == nil && idxLocked { + defer idxLock.Unlock() + } + if err != nil { + return err + } + b, err := json.MarshalIndent(i, "", " ") if err != nil { return err From 314bd19d1159834699d5ee58fe93d8df2d416e0c Mon Sep 17 00:00:00 2001 From: Artem Vdovin Date: Sun, 13 Jul 2025 22:48:39 +0500 Subject: [PATCH 002/114] update writing index files to writeAtomicFile Signed-off-by: Artem Vdovin --- pkg/repo/chartrepo.go | 41 ++++++----------------------------------- pkg/repo/index.go | 41 ----------------------------------------- 2 files changed, 6 insertions(+), 76 deletions(-) diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index bbcab2ae8..ed80a71c8 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -17,7 +17,7 @@ limitations under the License. package repo // import "helm.sh/helm/v4/pkg/repo" import ( - "context" + "bytes" "crypto/rand" "encoding/base64" "encoding/json" @@ -28,10 +28,8 @@ import ( "os" "path/filepath" "strings" - "time" - - "github.com/gofrs/flock" + "helm.sh/helm/v4/internal/fileutil" "helm.sh/helm/v4/pkg/getter" "helm.sh/helm/v4/pkg/helmpath" ) @@ -110,42 +108,15 @@ func (r *ChartRepository) DownloadIndexFile() (string, error) { for name := range indexFile.Entries { fmt.Fprintln(&charts, name) } - chartsFile := filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)) os.MkdirAll(filepath.Dir(chartsFile), 0755) - fname := filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name)) - os.MkdirAll(filepath.Dir(fname), 0755) - - // context for lock files - lockCtx, cancel := context.WithTimeout(context.Background(), 90*time.Second) - defer cancel() - - // lock the charts file - chLock := flock.New(filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)+".lock")) - chLocked, err := chLock.TryLockContext(lockCtx, 500*time.Millisecond) - if err == nil && chLocked { - defer chLock.Unlock() - } - if err != nil { - return "", err - } - - // write charts files after lock - os.WriteFile(chartsFile, []byte(charts.String()), 0644) - - // lock the index file - idxLock := flock.New(filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name)+".lock")) - idxLocked, err := idxLock.TryLockContext(lockCtx, 500*time.Millisecond) - if err == nil && idxLocked { - defer idxLock.Unlock() - } - if err != nil { - return "", err - } + fileutil.AtomicWriteFile(chartsFile, bytes.NewReader([]byte(charts.String())), 0644) // Create the index file in the cache directory - return fname, os.WriteFile(fname, index, 0644) + 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) } type findChartInRepoURLOptions struct { diff --git a/pkg/repo/index.go b/pkg/repo/index.go index b93816d71..c26d7581c 100644 --- a/pkg/repo/index.go +++ b/pkg/repo/index.go @@ -18,7 +18,6 @@ package repo import ( "bytes" - "context" "encoding/json" "errors" "fmt" @@ -31,7 +30,6 @@ import ( "time" "github.com/Masterminds/semver/v3" - "github.com/gofrs/flock" "sigs.k8s.io/yaml" "helm.sh/helm/v4/internal/fileutil" @@ -105,19 +103,6 @@ func NewIndexFile() *IndexFile { // LoadIndexFile takes a file at the given path and returns an IndexFile object func LoadIndexFile(path string) (*IndexFile, error) { - lockCtx, cancel := context.WithTimeout(context.Background(), 90*time.Second) - defer cancel() - - idxLock := flock.New(filepath.Join(path + ".lock")) - - idxLocked, err := idxLock.TryRLockContext(lockCtx, 500*time.Millisecond) - if err == nil && idxLocked { - defer idxLock.Unlock() - } - if err != nil { - return nil, err - } - b, err := os.ReadFile(path) if err != nil { return nil, err @@ -240,19 +225,6 @@ func (i IndexFile) Get(name, version string) (*ChartVersion, error) { // // The mode on the file is set to 'mode'. func (i IndexFile) WriteFile(dest string, mode os.FileMode) error { - lockCtx, cancel := context.WithTimeout(context.Background(), 90*time.Second) - defer cancel() - - idxLock := flock.New(dest + ".lock") - idxLocked, err := idxLock.TryLockContext(lockCtx, 500*time.Millisecond) - - if err == nil && idxLocked { - defer idxLock.Unlock() - } - if err != nil { - return err - } - b, err := yaml.Marshal(i) if err != nil { return err @@ -265,19 +237,6 @@ func (i IndexFile) WriteFile(dest string, mode os.FileMode) error { // // The mode on the file is set to 'mode'. func (i IndexFile) WriteJSONFile(dest string, mode os.FileMode) error { - lockCtx, cancel := context.WithTimeout(context.Background(), 90*time.Second) - defer cancel() - - idxLock := flock.New(dest + ".lock") - idxLocked, err := idxLock.TryLockContext(lockCtx, 500*time.Millisecond) - - if err == nil && idxLocked { - defer idxLock.Unlock() - } - if err != nil { - return err - } - b, err := json.MarshalIndent(i, "", " ") if err != nil { return err From 118d0eb697e52b90ad48e4e09529614be7ada4bc Mon Sep 17 00:00:00 2001 From: Artem Vdovin Date: Sun, 20 Jul 2025 11:42:20 +0500 Subject: [PATCH 003/114] add concurrency test on write & load index file Signed-off-by: Artem Vdovin --- pkg/repo/chartrepo_test.go | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/pkg/repo/chartrepo_test.go b/pkg/repo/chartrepo_test.go index 05e034dd8..ecb8a592e 100644 --- a/pkg/repo/chartrepo_test.go +++ b/pkg/repo/chartrepo_test.go @@ -22,8 +22,10 @@ import ( "net/http" "net/http/httptest" "os" + "path/filepath" "runtime" "strings" + "sync" "testing" "time" @@ -31,6 +33,7 @@ import ( "helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/helmpath" ) type CustomGetter struct { @@ -91,6 +94,60 @@ func TestIndexCustomSchemeDownload(t *testing.T) { } } +func TestConcurrenyDownloadIndex(t *testing.T) { + srv, err := startLocalServerForTests(nil) + if err != nil { + t.Fatal(err) + } + defer srv.Close() + + repo, err := NewChartRepository(&Entry{ + Name: "nginx", + URL: srv.URL, + }, getter.All(&cli.EnvSettings{})) + + if err != nil { + t.Fatalf("Problem loading chart repository from %s: %v", srv.URL, err) + } + 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) + } + + indexFName := filepath.Join(repo.CachePath, helmpath.CacheIndexFile(repo.Config.Name)) + + var wg sync.WaitGroup + + // Simultaneously start multiple goroutines that: + // 1) download index.yaml via DownloadIndexFile (write operation), + // 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() + idx, err := repo.DownloadIndexFile() + if err != nil { + t.Fatalf("Failed to download index file to %s: %v", idx, err) + } + }() + + wg.Add(1) + go func() { + defer wg.Done() + _, err := LoadIndexFile(indexFName) + if err != nil { + t.Fatalf("Failed to load index file: %v", err) + } + }() + } + wg.Wait() +} + // startLocalServerForTests Start the local helm server func startLocalServerForTests(handler http.Handler) (*httptest.Server, error) { if handler == nil { From 8068578266e87b1b587b4c05cc82eb98e797d992 Mon Sep 17 00:00:00 2001 From: zyfy29 Date: Thu, 28 Aug 2025 09:25:15 +0900 Subject: [PATCH 004/114] 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 03bb62f63dabb9f7f7db70e2e070e5e276384a8b Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Thu, 4 Sep 2025 15:40:59 +0200 Subject: [PATCH 005/114] jsonschema: warn and ignore unresolved URN $ref to match v3.18.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - v3.18.5 switched jsonschema and began resolving external $ref at compile-time, exposing missing urn handling (“no URLLoader registered for urn:…”). - Add urn scheme loader and pluggable URNResolver. If unresolved, log a warning and return a permissive true schema (back-compat). - Apply to pkg/chart/v2 and internal/chart/v3 validators. Not sure about that Note: external URNs need AddResource/Resolver (the CLI uses --map for this). Warning may appear twice since both validators run. Another strategy could be to add the option to import more "external schema" explicitly but it is another PR. Something similar to `--map` from jsonschema package (santhosh-tekuri/jsonschema@ed65924). Close: #31170 Signed-off-by: Benoit Tigeot --- pkg/chart/common/util/jsonschema.go | 25 ++++++++++++++++++++++++ pkg/chart/common/util/jsonschema_test.go | 13 ++++++++++++ 2 files changed, 38 insertions(+) diff --git a/pkg/chart/common/util/jsonschema.go b/pkg/chart/common/util/jsonschema.go index acd2ca100..827ce4128 100644 --- a/pkg/chart/common/util/jsonschema.go +++ b/pkg/chart/common/util/jsonschema.go @@ -127,6 +127,7 @@ func ValidateAgainstSingleSchema(values common.Values, schemaJSON []byte) (reter "file": jsonschema.FileLoader{}, "http": newHTTPURLLoader(), "https": newHTTPURLLoader(), + "urn": urnLoader{}, } compiler := jsonschema.NewCompiler() @@ -149,6 +150,30 @@ func ValidateAgainstSingleSchema(values common.Values, schemaJSON []byte) (reter return nil } +// URNResolverFunc allows SDK to plug a URN resolver. It must return a +// schema document compatible with the validator (e.g., result of +// jsonschema.UnmarshalJSON). +type URNResolverFunc func(urn string) (any, error) + +// URNResolver is the default resolver used by the URN loader. By default it +// returns a clear error. +var URNResolver URNResolverFunc = func(urn string) (any, error) { + return nil, fmt.Errorf("URN not resolved: %s", urn) +} + +// urnLoader implements resolution for the urn: scheme by delegating to +// URNResolver. If unresolved, it logs a warning and returns a permissive +// boolean-true schema to avoid hard failures (back-compat behavior). +type urnLoader struct{} + +func (l urnLoader) Load(urlStr string) (any, error) { + if doc, err := URNResolver(urlStr); err == nil && doc != nil { + return doc, nil + } + slog.Warn("unresolved URN reference ignored; using permissive schema", "urn", urlStr) + return jsonschema.UnmarshalJSON(strings.NewReader("true")) +} + // Note, JSONSchemaValidationError is used to wrap the error from the underlying // validation package so that Helm has a clean interface and the validation package // could be replaced without changing the Helm SDK API. diff --git a/pkg/chart/common/util/jsonschema_test.go b/pkg/chart/common/util/jsonschema_test.go index b34f9d514..f50cc81a6 100644 --- a/pkg/chart/common/util/jsonschema_test.go +++ b/pkg/chart/common/util/jsonschema_test.go @@ -286,3 +286,16 @@ func TestHTTPURLLoader_Load(t *testing.T) { } }) } + +// Test that an unresolved URN $ref is soft-ignored and validation succeeds. +// it mimics the behavior of Helm 3.18.4 +func TestValidateAgainstSingleSchema_UnresolvedURN_Ignored(t *testing.T) { + schema := []byte(`{ + "$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"} + if err := ValidateAgainstSingleSchema(vals, schema); err != nil { + t.Fatalf("expected no error when URN unresolved is ignored, got: %v", err) + } +} From 8025a397e6f61c3722e0355d296321d8eced8b13 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Thu, 4 Sep 2025 15:53:07 +0200 Subject: [PATCH 006/114] Ignore duplicated URN in logs Signed-off-by: Benoit Tigeot --- pkg/chart/common/util/jsonschema.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/chart/common/util/jsonschema.go b/pkg/chart/common/util/jsonschema.go index 827ce4128..d0298b1a2 100644 --- a/pkg/chart/common/util/jsonschema.go +++ b/pkg/chart/common/util/jsonschema.go @@ -24,6 +24,7 @@ import ( "log/slog" "net/http" "strings" + "sync" "time" "github.com/santhosh-tekuri/jsonschema/v6" @@ -166,11 +167,16 @@ var URNResolver URNResolverFunc = func(urn string) (any, error) { // boolean-true schema to avoid hard failures (back-compat behavior). type urnLoader struct{} +// warnedURNs ensures we log the unresolved-URN warning only once per URN. +var warnedURNs sync.Map + func (l urnLoader) Load(urlStr string) (any, error) { if doc, err := URNResolver(urlStr); err == nil && doc != nil { return doc, nil } - slog.Warn("unresolved URN reference ignored; using permissive schema", "urn", urlStr) + if _, loaded := warnedURNs.LoadOrStore(urlStr, struct{}{}); !loaded { + slog.Warn("unresolved URN reference ignored; using permissive schema", "urn", urlStr) + } return jsonschema.UnmarshalJSON(strings.NewReader("true")) } From 6181e0a873688e42e935147e0e3cb3769a067120 Mon Sep 17 00:00:00 2001 From: Tom Wieczorek Date: Mon, 22 May 2023 11:05:32 +0200 Subject: [PATCH 007/114] 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 ae4af69b9dbd5ad01dc9621a90e5b08327a3499e Mon Sep 17 00:00:00 2001 From: Artem Vdovin Date: Sat, 13 Sep 2025 16:23:23 +0500 Subject: [PATCH 008/114] fix test Signed-off-by: Artem Vdovin --- pkg/repo/v1/chartrepo_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/repo/v1/chartrepo_test.go b/pkg/repo/v1/chartrepo_test.go index ecb8a592e..e82d72279 100644 --- a/pkg/repo/v1/chartrepo_test.go +++ b/pkg/repo/v1/chartrepo_test.go @@ -132,7 +132,7 @@ func TestConcurrenyDownloadIndex(t *testing.T) { defer wg.Done() idx, err := repo.DownloadIndexFile() if err != nil { - t.Fatalf("Failed to download index file to %s: %v", idx, err) + t.Errorf("Failed to download index file to %s: %v", idx, err) } }() @@ -141,7 +141,7 @@ func TestConcurrenyDownloadIndex(t *testing.T) { defer wg.Done() _, err := LoadIndexFile(indexFName) if err != nil { - t.Fatalf("Failed to load index file: %v", err) + t.Errorf("Failed to load index file: %v", err) } }() } From d5d1ea3f5527b4794c624e84c152f28fad860e10 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Mon, 22 Sep 2025 17:26:57 -0700 Subject: [PATCH 009/114] fix: Fix Helm v4 release distribtion/get-helm-3 script Signed-off-by: George Jenkins --- .github/workflows/release.yml | 7 +++++-- scripts/get-helm-3 | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 21c527442..57f7e9167 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,8 +49,11 @@ jobs: # Push the latest semver tag, excluding prerelease tags LATEST_VERSION="$(git tag | sort -r --version-sort | grep '^v[0-9]' | grep -v '-' | head -n1)" echo "LATEST_VERSION=${LATEST_VERSION}" - echo "${LATEST_VERSION}" > _dist_versions/helm-latest-version - echo "${LATEST_VERSION}" > _dist_versions/helm3-latest-version + if [[ "${LATEST_VERSION}" != v4.* ]]; then + echo "Error: Latest version ${LATEST_VERSION} is not a v4 release" + exit 1 + fi + echo "${LATEST_VERSION}" > _dist_versions/helm4-latest-version - name: Upload Binaries uses: bacongobbler/azure-blob-storage-upload@50f7d898b7697e864130ea04c303ca38b5751c50 # pin@3.0.0 diff --git a/scripts/get-helm-3 b/scripts/get-helm-3 index 3aa44daee..2d67b53a4 100755 --- a/scripts/get-helm-3 +++ b/scripts/get-helm-3 @@ -114,7 +114,7 @@ verifySupported() { checkDesiredVersion() { if [ "x$DESIRED_VERSION" == "x" ]; then # Get tag from release URL - local latest_release_url="https://get.helm.sh/helm-latest-version" + local latest_release_url="https://get.helm.sh/helm3-latest-version" local latest_release_response="" if [ "${HAS_CURL}" == "true" ]; then latest_release_response=$( curl -L --silent --show-error --fail "$latest_release_url" 2>&1 || true ) From bee9c1a108e516922ef4a5cdb3365ff8c3b6b7cf Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Thu, 25 Sep 2025 09:29:57 -0600 Subject: [PATCH 010/114] chore: replace github.com/mitchellh/copystructure Signed-off-by: Terry Howe --- go.mod | 2 +- internal/chart/v3/util/dependencies.go | 3 +- internal/copystructure/copystructure.go | 120 ++++++ internal/copystructure/copystructure_test.go | 374 +++++++++++++++++++ pkg/chart/common/util/coalesce.go | 3 +- pkg/chart/v2/util/dependencies.go | 3 +- 6 files changed, 498 insertions(+), 7 deletions(-) create mode 100644 internal/copystructure/copystructure.go create mode 100644 internal/copystructure/copystructure_test.go diff --git a/go.mod b/go.mod index c08dabc3b..0d4b704be 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,6 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/mattn/go-shellwords v1.0.12 - github.com/mitchellh/copystructure v1.2.0 github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 @@ -112,6 +111,7 @@ require ( 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 + 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 diff --git a/internal/chart/v3/util/dependencies.go b/internal/chart/v3/util/dependencies.go index 489772115..cd7a8b78c 100644 --- a/internal/chart/v3/util/dependencies.go +++ b/internal/chart/v3/util/dependencies.go @@ -20,9 +20,8 @@ import ( "log/slog" "strings" - "github.com/mitchellh/copystructure" - chart "helm.sh/helm/v4/internal/chart/v3" + "helm.sh/helm/v4/internal/copystructure" "helm.sh/helm/v4/pkg/chart/common" "helm.sh/helm/v4/pkg/chart/common/util" ) diff --git a/internal/copystructure/copystructure.go b/internal/copystructure/copystructure.go new file mode 100644 index 000000000..d226975e7 --- /dev/null +++ b/internal/copystructure/copystructure.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 copystructure + +import ( + "fmt" + "reflect" +) + +// Copy performs a deep copy of the given interface{}. +// This implementation handles the specific use cases needed by Helm. +func Copy(src interface{}) (interface{}, error) { + if src == nil { + return make(map[string]interface{}), nil + } + return copyValue(reflect.ValueOf(src)) +} + +// copyValue handles copying using reflection for non-map types +func copyValue(original reflect.Value) (interface{}, error) { + switch original.Kind() { + case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, + reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, + reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, + reflect.Complex64, reflect.Complex128, reflect.String, reflect.Array: + return original.Interface(), nil + + case reflect.Interface: + if original.IsNil() { + return original.Interface(), nil + } + return copyValue(original.Elem()) + + case reflect.Map: + if original.IsNil() { + return original.Interface(), nil + } + copied := reflect.MakeMap(original.Type()) + + var err error + var child interface{} + iter := original.MapRange() + for iter.Next() { + key := iter.Key() + value := iter.Value() + + if value.Kind() == reflect.Interface && value.IsNil() { + copied.SetMapIndex(key, value) + continue + } + + child, err = copyValue(value) + if err != nil { + return nil, err + } + copied.SetMapIndex(key, reflect.ValueOf(child)) + } + return copied.Interface(), nil + + case reflect.Pointer: + if original.IsNil() { + return original.Interface(), nil + } + copied, err := copyValue(original.Elem()) + if err != nil { + return nil, err + } + ptr := reflect.New(original.Type().Elem()) + ptr.Elem().Set(reflect.ValueOf(copied)) + return ptr.Interface(), nil + + case reflect.Slice: + if original.IsNil() { + return original.Interface(), nil + } + copied := reflect.MakeSlice(original.Type(), original.Len(), original.Cap()) + for i := 0; i < original.Len(); i++ { + val, err := copyValue(original.Index(i)) + if err != nil { + return nil, err + } + copied.Index(i).Set(reflect.ValueOf(val)) + } + return copied.Interface(), nil + + case reflect.Struct: + copied := reflect.New(original.Type()).Elem() + for i := 0; i < original.NumField(); i++ { + elem, err := copyValue(original.Field(i)) + if err != nil { + return nil, err + } + copied.Field(i).Set(reflect.ValueOf(elem)) + } + return copied.Interface(), nil + + case reflect.Func, reflect.Chan, reflect.UnsafePointer: + if original.IsNil() { + return original.Interface(), nil + } + return original.Interface(), nil + + default: + return original.Interface(), fmt.Errorf("unsupported type %v", original) + } +} diff --git a/internal/copystructure/copystructure_test.go b/internal/copystructure/copystructure_test.go new file mode 100644 index 000000000..90678dfc1 --- /dev/null +++ b/internal/copystructure/copystructure_test.go @@ -0,0 +1,374 @@ +/* +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 copystructure + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCopy_Nil(t *testing.T) { + result, err := Copy(nil) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{}, result) +} + +func TestCopy_PrimitiveTypes(t *testing.T) { + tests := []struct { + name string + input interface{} + }{ + {"bool", true}, + {"int", 42}, + {"int8", int8(8)}, + {"int16", int16(16)}, + {"int32", int32(32)}, + {"int64", int64(64)}, + {"uint", uint(42)}, + {"uint8", uint8(8)}, + {"uint16", uint16(16)}, + {"uint32", uint32(32)}, + {"uint64", uint64(64)}, + {"float32", float32(3.14)}, + {"float64", 3.14159}, + {"complex64", complex64(1 + 2i)}, + {"complex128", 1 + 2i}, + {"string", "hello world"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := Copy(tt.input) + require.NoError(t, err) + assert.Equal(t, tt.input, result) + }) + } +} + +func TestCopy_Array(t *testing.T) { + input := [3]int{1, 2, 3} + result, err := Copy(input) + require.NoError(t, err) + assert.Equal(t, input, result) +} + +func TestCopy_Slice(t *testing.T) { + t.Run("slice of ints", func(t *testing.T) { + input := []int{1, 2, 3, 4, 5} + result, err := Copy(input) + require.NoError(t, err) + + resultSlice, ok := result.([]int) + require.True(t, ok) + assert.Equal(t, input, resultSlice) + + // Verify it's a deep copy by modifying original + input[0] = 999 + assert.Equal(t, 1, resultSlice[0]) + }) + + t.Run("slice of strings", func(t *testing.T) { + input := []string{"a", "b", "c"} + result, err := Copy(input) + require.NoError(t, err) + assert.Equal(t, input, result) + }) + + t.Run("nil slice", func(t *testing.T) { + var input []int + result, err := Copy(input) + require.NoError(t, err) + assert.Nil(t, result) + }) + + t.Run("slice of maps", func(t *testing.T) { + input := []map[string]interface{}{ + {"key1": "value1"}, + {"key2": "value2"}, + } + result, err := Copy(input) + require.NoError(t, err) + + resultSlice, ok := result.([]map[string]interface{}) + require.True(t, ok) + assert.Equal(t, input, resultSlice) + + // Verify deep copy + input[0]["key1"] = "modified" + assert.Equal(t, "value1", resultSlice[0]["key1"]) + }) +} + +func TestCopy_Map(t *testing.T) { + t.Run("map[string]interface{}", func(t *testing.T) { + input := map[string]interface{}{ + "string": "value", + "int": 42, + "bool": true, + "nested": map[string]interface{}{ + "inner": "value", + }, + } + + result, err := Copy(input) + require.NoError(t, err) + + resultMap, ok := result.(map[string]interface{}) + require.True(t, ok) + assert.Equal(t, input, resultMap) + + // Verify deep copy + input["string"] = "modified" + assert.Equal(t, "value", resultMap["string"]) + + nestedInput := input["nested"].(map[string]interface{}) + nestedResult := resultMap["nested"].(map[string]interface{}) + nestedInput["inner"] = "modified" + assert.Equal(t, "value", nestedResult["inner"]) + }) + + t.Run("map[string]string", func(t *testing.T) { + input := map[string]string{ + "key1": "value1", + "key2": "value2", + } + + result, err := Copy(input) + require.NoError(t, err) + assert.Equal(t, input, result) + }) + + t.Run("nil map", func(t *testing.T) { + var input map[string]interface{} + result, err := Copy(input) + require.NoError(t, err) + assert.Nil(t, result) + }) + + t.Run("map with nil values", func(t *testing.T) { + input := map[string]interface{}{ + "key1": "value1", + "key2": nil, + } + + result, err := Copy(input) + require.NoError(t, err) + + resultMap, ok := result.(map[string]interface{}) + require.True(t, ok) + assert.Equal(t, input, resultMap) + assert.Nil(t, resultMap["key2"]) + }) +} + +func TestCopy_Struct(t *testing.T) { + type TestStruct struct { + Name string + Age int + Active bool + Scores []int + Metadata map[string]interface{} + } + + input := TestStruct{ + Name: "John", + Age: 30, + Active: true, + Scores: []int{95, 87, 92}, + Metadata: map[string]interface{}{ + "level": "advanced", + "tags": []string{"go", "programming"}, + }, + } + + result, err := Copy(input) + require.NoError(t, err) + + resultStruct, ok := result.(TestStruct) + require.True(t, ok) + assert.Equal(t, input, resultStruct) + + // Verify deep copy + input.Name = "Modified" + input.Scores[0] = 999 + assert.Equal(t, "John", resultStruct.Name) + assert.Equal(t, 95, resultStruct.Scores[0]) +} + +func TestCopy_Pointer(t *testing.T) { + t.Run("pointer to int", func(t *testing.T) { + value := 42 + input := &value + + result, err := Copy(input) + require.NoError(t, err) + + resultPtr, ok := result.(*int) + require.True(t, ok) + assert.Equal(t, *input, *resultPtr) + + // Verify they point to different memory locations + assert.NotSame(t, input, resultPtr) + + // Verify deep copy + *input = 999 + assert.Equal(t, 42, *resultPtr) + }) + + t.Run("pointer to struct", func(t *testing.T) { + type Person struct { + Name string + Age int + } + + input := &Person{Name: "Alice", Age: 25} + + result, err := Copy(input) + require.NoError(t, err) + + resultPtr, ok := result.(*Person) + require.True(t, ok) + assert.Equal(t, *input, *resultPtr) + assert.NotSame(t, input, resultPtr) + }) + + t.Run("nil pointer", func(t *testing.T) { + var input *int + result, err := Copy(input) + require.NoError(t, err) + assert.Nil(t, result) + }) +} + +func TestCopy_Interface(t *testing.T) { + t.Run("interface{} with value", func(t *testing.T) { + var input interface{} = "hello" + result, err := Copy(input) + require.NoError(t, err) + assert.Equal(t, input, result) + }) + + t.Run("nil interface{}", func(t *testing.T) { + var input interface{} + result, err := Copy(input) + require.NoError(t, err) + // Copy(nil) returns an empty map according to the implementation + assert.Equal(t, map[string]interface{}{}, result) + }) + + t.Run("interface{} with complex value", func(t *testing.T) { + var input interface{} = map[string]interface{}{ + "key": "value", + "nested": map[string]interface{}{ + "inner": 42, + }, + } + + result, err := Copy(input) + require.NoError(t, err) + assert.Equal(t, input, result) + }) +} + +func TestCopy_ComplexNested(t *testing.T) { + input := map[string]interface{}{ + "users": []map[string]interface{}{ + { + "name": "Alice", + "age": 30, + "addresses": []map[string]interface{}{ + {"type": "home", "city": "NYC"}, + {"type": "work", "city": "SF"}, + }, + }, + { + "name": "Bob", + "age": 25, + "addresses": []map[string]interface{}{ + {"type": "home", "city": "LA"}, + }, + }, + }, + "metadata": map[string]interface{}{ + "version": "1.0", + "flags": []bool{true, false, true}, + }, + } + + result, err := Copy(input) + require.NoError(t, err) + + resultMap, ok := result.(map[string]interface{}) + require.True(t, ok) + assert.Equal(t, input, resultMap) + + // Verify deep copy by modifying nested values + users := input["users"].([]map[string]interface{}) + addresses := users[0]["addresses"].([]map[string]interface{}) + addresses[0]["city"] = "Modified" + + resultUsers := resultMap["users"].([]map[string]interface{}) + resultAddresses := resultUsers[0]["addresses"].([]map[string]interface{}) + assert.Equal(t, "NYC", resultAddresses[0]["city"]) +} + +func TestCopy_Functions(t *testing.T) { + t.Run("function", func(t *testing.T) { + input := func() string { return "hello" } + result, err := Copy(input) + require.NoError(t, err) + + // Functions should be copied as-is (same reference) + resultFunc, ok := result.(func() string) + require.True(t, ok) + assert.Equal(t, input(), resultFunc()) + }) + + t.Run("nil function", func(t *testing.T) { + var input func() + result, err := Copy(input) + require.NoError(t, err) + assert.Nil(t, result) + }) +} + +func TestCopy_Channels(t *testing.T) { + t.Run("channel", func(t *testing.T) { + input := make(chan int, 1) + input <- 42 + + result, err := Copy(input) + require.NoError(t, err) + + // Channels should be copied as-is (same reference) + resultChan, ok := result.(chan int) + require.True(t, ok) + + // Since channels are copied as references, verify we can read from the result channel + value := <-resultChan + assert.Equal(t, 42, value) + }) + + t.Run("nil channel", func(t *testing.T) { + var input chan int + result, err := Copy(input) + require.NoError(t, err) + assert.Nil(t, result) + }) +} diff --git a/pkg/chart/common/util/coalesce.go b/pkg/chart/common/util/coalesce.go index 5bfa1c608..07794a04a 100644 --- a/pkg/chart/common/util/coalesce.go +++ b/pkg/chart/common/util/coalesce.go @@ -21,8 +21,7 @@ import ( "log" "maps" - "github.com/mitchellh/copystructure" - + "helm.sh/helm/v4/internal/copystructure" chart "helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart/common" ) diff --git a/pkg/chart/v2/util/dependencies.go b/pkg/chart/v2/util/dependencies.go index a52f09f82..294b782f8 100644 --- a/pkg/chart/v2/util/dependencies.go +++ b/pkg/chart/v2/util/dependencies.go @@ -20,8 +20,7 @@ import ( "log/slog" "strings" - "github.com/mitchellh/copystructure" - + "helm.sh/helm/v4/internal/copystructure" "helm.sh/helm/v4/pkg/chart/common" "helm.sh/helm/v4/pkg/chart/common/util" chart "helm.sh/helm/v4/pkg/chart/v2" From 269a32a6d2089bdddf7b87e185811a661a041ad9 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Mon, 13 Oct 2025 10:31:27 +0200 Subject: [PATCH 011/114] test: protect unknown hook delete policies Signed-off-by: Marcin Owsiany --- pkg/action/hooks_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pkg/action/hooks_test.go b/pkg/action/hooks_test.go index 9502737d7..05b97db9c 100644 --- a/pkg/action/hooks_test.go +++ b/pkg/action/hooks_test.go @@ -408,3 +408,35 @@ data: }) } } + +func TestConfiguration_hookSetDeletePolicy(t *testing.T) { + tests := map[string]struct { + policies []release.HookDeletePolicy + expected []release.HookDeletePolicy + }{ + "no polices specified result in the default policy": { + policies: nil, + expected: []release.HookDeletePolicy{ + release.HookBeforeHookCreation, + }, + }, + "unknown policy is untouched": { + policies: []release.HookDeletePolicy{ + release.HookDeletePolicy("never"), + }, + expected: []release.HookDeletePolicy{ + release.HookDeletePolicy("never"), + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + cfg := &Configuration{} + h := &release.Hook{ + DeletePolicies: tt.policies, + } + cfg.hookSetDeletePolicy(h) + assert.Equal(t, tt.expected, h.DeletePolicies) + }) + } +} From ffb3940011c77dd1ef930854e7875dd07c4b8cd4 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Mon, 13 Oct 2025 14:28:42 -0600 Subject: [PATCH 012/114] rename interface{} to any Signed-off-by: Terry Howe --- internal/copystructure/copystructure.go | 10 +-- internal/copystructure/copystructure_test.go | 66 ++++++++++---------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/internal/copystructure/copystructure.go b/internal/copystructure/copystructure.go index d226975e7..aa5510298 100644 --- a/internal/copystructure/copystructure.go +++ b/internal/copystructure/copystructure.go @@ -21,17 +21,17 @@ import ( "reflect" ) -// Copy performs a deep copy of the given interface{}. +// Copy performs a deep copy of the given src. // This implementation handles the specific use cases needed by Helm. -func Copy(src interface{}) (interface{}, error) { +func Copy(src any) (any, error) { if src == nil { - return make(map[string]interface{}), nil + return make(map[string]any), nil } return copyValue(reflect.ValueOf(src)) } // copyValue handles copying using reflection for non-map types -func copyValue(original reflect.Value) (interface{}, error) { +func copyValue(original reflect.Value) (any, error) { switch original.Kind() { case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, @@ -52,7 +52,7 @@ func copyValue(original reflect.Value) (interface{}, error) { copied := reflect.MakeMap(original.Type()) var err error - var child interface{} + var child any iter := original.MapRange() for iter.Next() { key := iter.Key() diff --git a/internal/copystructure/copystructure_test.go b/internal/copystructure/copystructure_test.go index 90678dfc1..d1708dc75 100644 --- a/internal/copystructure/copystructure_test.go +++ b/internal/copystructure/copystructure_test.go @@ -26,13 +26,13 @@ import ( func TestCopy_Nil(t *testing.T) { result, err := Copy(nil) require.NoError(t, err) - assert.Equal(t, map[string]interface{}{}, result) + assert.Equal(t, map[string]any{}, result) } func TestCopy_PrimitiveTypes(t *testing.T) { tests := []struct { name string - input interface{} + input any }{ {"bool", true}, {"int", 42}, @@ -98,14 +98,14 @@ func TestCopy_Slice(t *testing.T) { }) t.Run("slice of maps", func(t *testing.T) { - input := []map[string]interface{}{ + input := []map[string]any{ {"key1": "value1"}, {"key2": "value2"}, } result, err := Copy(input) require.NoError(t, err) - resultSlice, ok := result.([]map[string]interface{}) + resultSlice, ok := result.([]map[string]any) require.True(t, ok) assert.Equal(t, input, resultSlice) @@ -116,12 +116,12 @@ func TestCopy_Slice(t *testing.T) { } func TestCopy_Map(t *testing.T) { - t.Run("map[string]interface{}", func(t *testing.T) { - input := map[string]interface{}{ + t.Run("map[string]any", func(t *testing.T) { + input := map[string]any{ "string": "value", "int": 42, "bool": true, - "nested": map[string]interface{}{ + "nested": map[string]any{ "inner": "value", }, } @@ -129,7 +129,7 @@ func TestCopy_Map(t *testing.T) { result, err := Copy(input) require.NoError(t, err) - resultMap, ok := result.(map[string]interface{}) + resultMap, ok := result.(map[string]any) require.True(t, ok) assert.Equal(t, input, resultMap) @@ -137,8 +137,8 @@ func TestCopy_Map(t *testing.T) { input["string"] = "modified" assert.Equal(t, "value", resultMap["string"]) - nestedInput := input["nested"].(map[string]interface{}) - nestedResult := resultMap["nested"].(map[string]interface{}) + nestedInput := input["nested"].(map[string]any) + nestedResult := resultMap["nested"].(map[string]any) nestedInput["inner"] = "modified" assert.Equal(t, "value", nestedResult["inner"]) }) @@ -155,14 +155,14 @@ func TestCopy_Map(t *testing.T) { }) t.Run("nil map", func(t *testing.T) { - var input map[string]interface{} + var input map[string]any result, err := Copy(input) require.NoError(t, err) assert.Nil(t, result) }) t.Run("map with nil values", func(t *testing.T) { - input := map[string]interface{}{ + input := map[string]any{ "key1": "value1", "key2": nil, } @@ -170,7 +170,7 @@ func TestCopy_Map(t *testing.T) { result, err := Copy(input) require.NoError(t, err) - resultMap, ok := result.(map[string]interface{}) + resultMap, ok := result.(map[string]any) require.True(t, ok) assert.Equal(t, input, resultMap) assert.Nil(t, resultMap["key2"]) @@ -183,7 +183,7 @@ func TestCopy_Struct(t *testing.T) { Age int Active bool Scores []int - Metadata map[string]interface{} + Metadata map[string]any } input := TestStruct{ @@ -191,7 +191,7 @@ func TestCopy_Struct(t *testing.T) { Age: 30, Active: true, Scores: []int{95, 87, 92}, - Metadata: map[string]interface{}{ + Metadata: map[string]any{ "level": "advanced", "tags": []string{"go", "programming"}, }, @@ -257,25 +257,25 @@ func TestCopy_Pointer(t *testing.T) { } func TestCopy_Interface(t *testing.T) { - t.Run("interface{} with value", func(t *testing.T) { - var input interface{} = "hello" + t.Run("any with value", func(t *testing.T) { + var input any = "hello" result, err := Copy(input) require.NoError(t, err) assert.Equal(t, input, result) }) - t.Run("nil interface{}", func(t *testing.T) { - var input interface{} + t.Run("nil any", func(t *testing.T) { + var input any result, err := Copy(input) require.NoError(t, err) // Copy(nil) returns an empty map according to the implementation - assert.Equal(t, map[string]interface{}{}, result) + assert.Equal(t, map[string]any{}, result) }) - t.Run("interface{} with complex value", func(t *testing.T) { - var input interface{} = map[string]interface{}{ + t.Run("any with complex value", func(t *testing.T) { + var input any = map[string]any{ "key": "value", - "nested": map[string]interface{}{ + "nested": map[string]any{ "inner": 42, }, } @@ -287,12 +287,12 @@ func TestCopy_Interface(t *testing.T) { } func TestCopy_ComplexNested(t *testing.T) { - input := map[string]interface{}{ - "users": []map[string]interface{}{ + input := map[string]any{ + "users": []map[string]any{ { "name": "Alice", "age": 30, - "addresses": []map[string]interface{}{ + "addresses": []map[string]any{ {"type": "home", "city": "NYC"}, {"type": "work", "city": "SF"}, }, @@ -300,12 +300,12 @@ func TestCopy_ComplexNested(t *testing.T) { { "name": "Bob", "age": 25, - "addresses": []map[string]interface{}{ + "addresses": []map[string]any{ {"type": "home", "city": "LA"}, }, }, }, - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "version": "1.0", "flags": []bool{true, false, true}, }, @@ -314,17 +314,17 @@ func TestCopy_ComplexNested(t *testing.T) { result, err := Copy(input) require.NoError(t, err) - resultMap, ok := result.(map[string]interface{}) + resultMap, ok := result.(map[string]any) require.True(t, ok) assert.Equal(t, input, resultMap) // Verify deep copy by modifying nested values - users := input["users"].([]map[string]interface{}) - addresses := users[0]["addresses"].([]map[string]interface{}) + users := input["users"].([]map[string]any) + addresses := users[0]["addresses"].([]map[string]any) addresses[0]["city"] = "Modified" - resultUsers := resultMap["users"].([]map[string]interface{}) - resultAddresses := resultUsers[0]["addresses"].([]map[string]interface{}) + resultUsers := resultMap["users"].([]map[string]any) + resultAddresses := resultUsers[0]["addresses"].([]map[string]any) assert.Equal(t, "NYC", resultAddresses[0]["city"]) } From 0d6de28bf446b4d1b2cb71e0441e5c8ebc9e91c9 Mon Sep 17 00:00:00 2001 From: tison Date: Fri, 24 Oct 2025 15:02:25 +0800 Subject: [PATCH 013/114] chore: fix typo of public field Signed-off-by: tison --- pkg/action/package.go | 2 +- pkg/cmd/package.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/action/package.go b/pkg/action/package.go index 92a9a8cb6..963883707 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -57,7 +57,7 @@ type Package struct { CertFile string KeyFile string CaFile string - InsecureSkipTLSverify bool + InsecureSkipTLSVerify bool } const ( diff --git a/pkg/cmd/package.go b/pkg/cmd/package.go index fc56e936a..36a7dff73 100644 --- a/pkg/cmd/package.go +++ b/pkg/cmd/package.go @@ -76,7 +76,7 @@ func newPackageCmd(out io.Writer) *cobra.Command { } registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) + client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) } @@ -130,7 +130,7 @@ func newPackageCmd(out io.Writer) *cobra.Command { f.StringVar(&client.Password, "password", "", "chart repository password where to locate the requested chart") f.StringVar(&client.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&client.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file") - f.BoolVar(&client.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") + f.BoolVar(&client.InsecureSkipTLSVerify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") f.BoolVar(&client.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download") f.StringVar(&client.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") From 6ef79bb8d54bd4881e60d3e78d633b021f1079ec Mon Sep 17 00:00:00 2001 From: Paul Van Laer Date: Thu, 30 Oct 2025 18:00:08 +0100 Subject: [PATCH 014/114] 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 015/114] 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 90d01915f9c210c7179fd2f9c34edab04619b204 Mon Sep 17 00:00:00 2001 From: tison Date: Tue, 4 Nov 2025 11:33:42 +0800 Subject: [PATCH 016/114] for all other similar cases Signed-off-by: tison --- internal/tlsutil/tls.go | 8 ++++---- internal/tlsutil/tls_test.go | 8 ++++---- pkg/action/dependency.go | 2 +- pkg/action/install.go | 6 +++--- pkg/action/pull.go | 4 ++-- pkg/action/push.go | 6 +++--- pkg/cmd/dependency.go | 2 +- pkg/cmd/dependency_build.go | 2 +- pkg/cmd/dependency_update.go | 2 +- pkg/cmd/flags.go | 2 +- pkg/cmd/install.go | 2 +- pkg/cmd/plugin_install.go | 6 +++--- pkg/cmd/pull.go | 2 +- pkg/cmd/push.go | 8 ++++---- pkg/cmd/repo_add.go | 6 +++--- pkg/cmd/root.go | 10 +++++----- pkg/cmd/show.go | 2 +- pkg/cmd/template.go | 2 +- pkg/cmd/upgrade.go | 2 +- pkg/downloader/manager.go | 2 +- pkg/getter/httpgetter_test.go | 10 +++++----- pkg/pusher/ocipusher.go | 4 ++-- pkg/pusher/ocipusher_test.go | 12 ++++++------ pkg/pusher/pusher.go | 4 ++-- pkg/repo/v1/chartrepo.go | 14 +++++++------- pkg/repo/v1/chartrepo_test.go | 2 +- 26 files changed, 65 insertions(+), 65 deletions(-) diff --git a/internal/tlsutil/tls.go b/internal/tlsutil/tls.go index 645834c29..88f26d47b 100644 --- a/internal/tlsutil/tls.go +++ b/internal/tlsutil/tls.go @@ -26,16 +26,16 @@ import ( ) type TLSConfigOptions struct { - insecureSkipTLSverify bool + insecureSkipTLSVerify bool certPEMBlock, keyPEMBlock []byte caPEMBlock []byte } type TLSConfigOption func(options *TLSConfigOptions) error -func WithInsecureSkipVerify(insecureSkipTLSverify bool) TLSConfigOption { +func WithInsecureSkipVerify(insecureSkipTLSVerify bool) TLSConfigOption { return func(options *TLSConfigOptions) error { - options.insecureSkipTLSverify = insecureSkipTLSverify + options.insecureSkipTLSVerify = insecureSkipTLSVerify return nil } @@ -97,7 +97,7 @@ func NewTLSConfig(options ...TLSConfigOption) (*tls.Config, error) { } config := tls.Config{ - InsecureSkipVerify: to.insecureSkipTLSverify, + InsecureSkipVerify: to.insecureSkipTLSVerify, } if len(to.certPEMBlock) > 0 && len(to.keyPEMBlock) > 0 { diff --git a/internal/tlsutil/tls_test.go b/internal/tlsutil/tls_test.go index 3d7e75c86..f16eb218f 100644 --- a/internal/tlsutil/tls_test.go +++ b/internal/tlsutil/tls_test.go @@ -42,11 +42,11 @@ func TestNewTLSConfig(t *testing.T) { certFile := testfile(t, testCertFile) keyFile := testfile(t, testKeyFile) caCertFile := testfile(t, testCaCertFile) - insecureSkipTLSverify := false + insecureSkipTLSVerify := false { cfg, err := NewTLSConfig( - WithInsecureSkipVerify(insecureSkipTLSverify), + WithInsecureSkipVerify(insecureSkipTLSVerify), WithCertKeyPairFiles(certFile, keyFile), WithCAFile(caCertFile), ) @@ -66,7 +66,7 @@ func TestNewTLSConfig(t *testing.T) { } { cfg, err := NewTLSConfig( - WithInsecureSkipVerify(insecureSkipTLSverify), + WithInsecureSkipVerify(insecureSkipTLSVerify), WithCAFile(caCertFile), ) if err != nil { @@ -86,7 +86,7 @@ func TestNewTLSConfig(t *testing.T) { { cfg, err := NewTLSConfig( - WithInsecureSkipVerify(insecureSkipTLSverify), + WithInsecureSkipVerify(insecureSkipTLSVerify), WithCertKeyPairFiles(certFile, keyFile), ) if err != nil { diff --git a/pkg/action/dependency.go b/pkg/action/dependency.go index 03c370c8e..b12887bde 100644 --- a/pkg/action/dependency.go +++ b/pkg/action/dependency.go @@ -43,7 +43,7 @@ type Dependency struct { CertFile string KeyFile string CaFile string - InsecureSkipTLSverify bool + InsecureSkipTLSVerify bool PlainHTTP bool } diff --git a/pkg/action/install.go b/pkg/action/install.go index 87752684c..eaaa1a0ae 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -139,7 +139,7 @@ type ChartPathOptions struct { CaFile string // --ca-file CertFile string // --cert-file KeyFile string // --key-file - InsecureSkipTLSverify bool // --insecure-skip-verify + InsecureSkipTLSVerify bool // --insecure-skip-verify PlainHTTP bool // --plain-http Keyring string // --keyring Password string // --password @@ -886,7 +886,7 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) ( Options: []getter.Option{ getter.WithPassCredentialsAll(c.PassCredentialsAll), getter.WithTLSClientConfig(c.CertFile, c.KeyFile, c.CaFile), - getter.WithInsecureSkipVerifyTLS(c.InsecureSkipTLSverify), + getter.WithInsecureSkipVerifyTLS(c.InsecureSkipTLSVerify), getter.WithPlainHTTP(c.PlainHTTP), getter.WithBasicAuth(c.Username, c.Password), }, @@ -911,7 +911,7 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) ( repo.WithChartVersion(version), repo.WithClientTLS(c.CertFile, c.KeyFile, c.CaFile), repo.WithUsernamePassword(c.Username, c.Password), - repo.WithInsecureSkipTLSverify(c.InsecureSkipTLSverify), + repo.WithInsecureSkipTLSVerify(c.InsecureSkipTLSVerify), repo.WithPassCredentialsAll(c.PassCredentialsAll), ) if err != nil { diff --git a/pkg/action/pull.go b/pkg/action/pull.go index be71d0ed0..dd051167b 100644 --- a/pkg/action/pull.go +++ b/pkg/action/pull.go @@ -82,7 +82,7 @@ func (p *Pull) Run(chartRef string) (string, error) { getter.WithBasicAuth(p.Username, p.Password), getter.WithPassCredentialsAll(p.PassCredentialsAll), getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile), - getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSverify), + getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSVerify), getter.WithPlainHTTP(p.PlainHTTP), }, RegistryClient: p.cfg.RegistryClient, @@ -124,7 +124,7 @@ func (p *Pull) Run(chartRef string) (string, error) { repo.WithChartVersion(p.Version), repo.WithClientTLS(p.CertFile, p.KeyFile, p.CaFile), repo.WithUsernamePassword(p.Username, p.Password), - repo.WithInsecureSkipTLSverify(p.InsecureSkipTLSverify), + repo.WithInsecureSkipTLSVerify(p.InsecureSkipTLSVerify), repo.WithPassCredentialsAll(p.PassCredentialsAll), ) if err != nil { diff --git a/pkg/action/push.go b/pkg/action/push.go index 35472415c..0c7148f65 100644 --- a/pkg/action/push.go +++ b/pkg/action/push.go @@ -35,7 +35,7 @@ type Push struct { certFile string keyFile string caFile string - insecureSkipTLSverify bool + insecureSkipTLSVerify bool plainHTTP bool out io.Writer } @@ -62,7 +62,7 @@ func WithTLSClientConfig(certFile, keyFile, caFile string) PushOpt { // WithInsecureSkipTLSVerify determines if a TLS Certificate will be checked func WithInsecureSkipTLSVerify(insecureSkipTLSVerify bool) PushOpt { return func(p *Push) { - p.insecureSkipTLSverify = insecureSkipTLSVerify + p.insecureSkipTLSVerify = insecureSkipTLSVerify } } @@ -98,7 +98,7 @@ func (p *Push) Run(chartRef string, remote string) (string, error) { Pushers: pusher.All(p.Settings), Options: []pusher.Option{ pusher.WithTLSClientConfig(p.certFile, p.keyFile, p.caFile), - pusher.WithInsecureSkipTLSVerify(p.insecureSkipTLSverify), + pusher.WithInsecureSkipTLSVerify(p.insecureSkipTLSVerify), pusher.WithPlainHTTP(p.plainHTTP), }, } diff --git a/pkg/cmd/dependency.go b/pkg/cmd/dependency.go index 34bbff6be..5978c902a 100644 --- a/pkg/cmd/dependency.go +++ b/pkg/cmd/dependency.go @@ -130,7 +130,7 @@ func addDependencySubcommandFlags(f *pflag.FlagSet, client *action.Dependency) { f.StringVar(&client.Password, "password", "", "chart repository password where to locate the requested chart") f.StringVar(&client.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&client.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file") - f.BoolVar(&client.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") + f.BoolVar(&client.InsecureSkipTLSVerify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") f.BoolVar(&client.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download") f.StringVar(&client.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") } diff --git a/pkg/cmd/dependency_build.go b/pkg/cmd/dependency_build.go index 320fe12ae..7e5c731b7 100644 --- a/pkg/cmd/dependency_build.go +++ b/pkg/cmd/dependency_build.go @@ -55,7 +55,7 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command { chartpath = filepath.Clean(args[0]) } registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) + 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 b534fb48a..7f805c37b 100644 --- a/pkg/cmd/dependency_update.go +++ b/pkg/cmd/dependency_update.go @@ -59,7 +59,7 @@ func newDependencyUpdateCmd(_ *action.Configuration, out io.Writer) *cobra.Comma chartpath = filepath.Clean(args[0]) } registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) + client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) } diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index b20772ef9..5836a6af9 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -110,7 +110,7 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) { f.StringVar(&c.Password, "password", "", "chart repository password where to locate the requested chart") f.StringVar(&c.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&c.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file") - f.BoolVar(&c.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") + f.BoolVar(&c.InsecureSkipTLSVerify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") f.BoolVar(&c.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download") f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.BoolVar(&c.PassCredentialsAll, "pass-credentials", false, "pass credentials to all domains") diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index 295f1ae37..e5d311394 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -144,7 +144,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, RunE: func(cmd *cobra.Command, args []string) error { registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) + client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) } diff --git a/pkg/cmd/plugin_install.go b/pkg/cmd/plugin_install.go index 0abefa76b..a45c4a3de 100644 --- a/pkg/cmd/plugin_install.go +++ b/pkg/cmd/plugin_install.go @@ -40,7 +40,7 @@ type pluginInstallOptions struct { certFile string keyFile string caFile string - insecureSkipTLSverify bool + insecureSkipTLSVerify bool plainHTTP bool password string username string @@ -88,7 +88,7 @@ func newPluginInstallCmd(out io.Writer) *cobra.Command { cmd.Flags().StringVar(&o.certFile, "cert-file", "", "identify registry client using this SSL certificate file") cmd.Flags().StringVar(&o.keyFile, "key-file", "", "identify registry client using this SSL key file") cmd.Flags().StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") - cmd.Flags().BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the plugin download") + cmd.Flags().BoolVar(&o.insecureSkipTLSVerify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the plugin download") cmd.Flags().BoolVar(&o.plainHTTP, "plain-http", false, "use insecure HTTP connections for the plugin download") cmd.Flags().StringVar(&o.username, "username", "", "registry username") cmd.Flags().StringVar(&o.password, "password", "", "registry password") @@ -106,7 +106,7 @@ func (o *pluginInstallOptions) newInstallerForSource() (installer.Installer, err // Build getter options for OCI options := []getter.Option{ getter.WithTLSClientConfig(o.certFile, o.keyFile, o.caFile), - getter.WithInsecureSkipVerifyTLS(o.insecureSkipTLSverify), + getter.WithInsecureSkipVerifyTLS(o.insecureSkipTLSVerify), getter.WithPlainHTTP(o.plainHTTP), getter.WithBasicAuth(o.username, o.password), } diff --git a/pkg/cmd/pull.go b/pkg/cmd/pull.go index e3d93c049..922698c6c 100644 --- a/pkg/cmd/pull.go +++ b/pkg/cmd/pull.go @@ -66,7 +66,7 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) + 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 94d322b9d..f57a7c52f 100644 --- a/pkg/cmd/push.go +++ b/pkg/cmd/push.go @@ -38,7 +38,7 @@ type registryPushOptions struct { certFile string keyFile string caFile string - insecureSkipTLSverify bool + insecureSkipTLSVerify bool plainHTTP bool password string username string @@ -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, + o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSVerify, o.plainHTTP, o.username, o.password, ) if err != nil { @@ -82,7 +82,7 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { remote := args[1] client := action.NewPushWithOpts(action.WithPushConfig(cfg), action.WithTLSClientConfig(o.certFile, o.keyFile, o.caFile), - action.WithInsecureSkipTLSVerify(o.insecureSkipTLSverify), + action.WithInsecureSkipTLSVerify(o.insecureSkipTLSVerify), action.WithPlainHTTP(o.plainHTTP), action.WithPushOptWriter(out)) client.Settings = settings @@ -99,7 +99,7 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.StringVar(&o.certFile, "cert-file", "", "identify registry client using this SSL certificate file") f.StringVar(&o.keyFile, "key-file", "", "identify registry client using this SSL key file") f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") - f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart upload") + f.BoolVar(&o.insecureSkipTLSVerify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart upload") f.BoolVar(&o.plainHTTP, "plain-http", false, "use insecure HTTP connections for the chart upload") f.StringVar(&o.username, "username", "", "chart repository username where to locate the requested chart") f.StringVar(&o.password, "password", "", "chart repository password where to locate the requested chart") diff --git a/pkg/cmd/repo_add.go b/pkg/cmd/repo_add.go index 00e698daf..3fc1a7249 100644 --- a/pkg/cmd/repo_add.go +++ b/pkg/cmd/repo_add.go @@ -57,7 +57,7 @@ type repoAddOptions struct { certFile string keyFile string caFile string - insecureSkipTLSverify bool + insecureSkipTLSVerify bool repoFile string repoCache string @@ -94,7 +94,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") - f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the repository") + f.BoolVar(&o.insecureSkipTLSVerify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the repository") f.BoolVar(&o.allowDeprecatedRepos, "allow-deprecated-repos", false, "by default, this command will not allow adding official repos that have been permanently deleted. This disables that behavior") f.BoolVar(&o.passCredentialsAll, "pass-credentials", false, "pass credentials to all domains") f.DurationVar(&o.timeout, "timeout", getter.DefaultHTTPTimeout*time.Second, "time to wait for the index file download to complete") @@ -177,7 +177,7 @@ func (o *repoAddOptions) run(out io.Writer) error { CertFile: o.certFile, KeyFile: o.keyFile, CAFile: o.caFile, - InsecureSkipTLSverify: o.insecureSkipTLSverify, + InsecureSkipTLSVerify: o.insecureSkipTLSVerify, } // Check if the repo name is legal diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 48dbd760d..bb04272f7 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -393,10 +393,10 @@ func checkForExpiredRepos(repofile string) { } func newRegistryClient( - certFile, keyFile, caFile string, insecureSkipTLSverify, plainHTTP bool, username, password string, + 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) + if certFile != "" && keyFile != "" || caFile != "" || insecureSkipTLSVerify { + registryClient, err := newRegistryClientWithTLS(certFile, keyFile, caFile, insecureSkipTLSVerify, username, password) if err != nil { return nil, err } @@ -430,10 +430,10 @@ func newDefaultRegistryClient(plainHTTP bool, username, password string) (*regis } func newRegistryClientWithTLS( - certFile, keyFile, caFile string, insecureSkipTLSverify bool, username, password string, + certFile, keyFile, caFile string, insecureSkipTLSVerify bool, username, password string, ) (*registry.Client, error) { tlsConf, err := tlsutil.NewTLSConfig( - tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify), + tlsutil.WithInsecureSkipVerify(insecureSkipTLSVerify), tlsutil.WithCertKeyPairFiles(certFile, keyFile), tlsutil.WithCAFile(caFile), ) diff --git a/pkg/cmd/show.go b/pkg/cmd/show.go index 1c7e7be44..d7249c3fe 100644 --- a/pkg/cmd/show.go +++ b/pkg/cmd/show.go @@ -227,7 +227,7 @@ func runShow(args []string, client *action.Show) (string, error) { func addRegistryClient(client *action.Show) error { registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) + 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 3ede31077..cf68c6c46 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -76,7 +76,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) + 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 f32493e87..92f130f60 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 { client.Namespace = settings.Namespace() registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) + client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) } diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index d41b8fdb4..6a0b91cd4 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -755,7 +755,7 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]* username = cr.Config.Username password = cr.Config.Password passcredentialsall = cr.Config.PassCredentialsAll - insecureskiptlsverify = cr.Config.InsecureSkipTLSverify + insecureskiptlsverify = cr.Config.InsecureSkipTLSVerify caFile = cr.Config.CAFile certFile = cr.Config.CertFile keyFile = cr.Config.KeyFile diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index 96bfa1ece..b27b9f5d2 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -306,11 +306,11 @@ func TestDownload(t *testing.T) { func TestDownloadTLS(t *testing.T) { cd := "../../testdata" ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem") - insecureSkipTLSverify := false + insecureSkipTLSVerify := false tlsSrv := httptest.NewUnstartedServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) tlsConf, err := tlsutil.NewTLSConfig( - tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify), + tlsutil.WithInsecureSkipVerify(insecureSkipTLSVerify), tlsutil.WithCertKeyPairFiles(pub, priv), tlsutil.WithCAFile(ca), ) @@ -359,14 +359,14 @@ func TestDownloadTLS(t *testing.T) { func TestDownloadTLSWithRedirect(t *testing.T) { cd := "../../testdata" srv2Resp := "hello" - insecureSkipTLSverify := false + insecureSkipTLSVerify := false // Server 2 that will actually fulfil the request. ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "localhost-crt.pem"), filepath.Join(cd, "key.pem") tlsConf, err := tlsutil.NewTLSConfig( tlsutil.WithCAFile(ca), tlsutil.WithCertKeyPairFiles(pub, priv), - tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify), + tlsutil.WithInsecureSkipVerify(insecureSkipTLSVerify), ) if err != nil { @@ -387,7 +387,7 @@ func TestDownloadTLSWithRedirect(t *testing.T) { tlsConf, err = tlsutil.NewTLSConfig( tlsutil.WithCAFile(ca), tlsutil.WithCertKeyPairFiles(pub, priv), - tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify), + tlsutil.WithInsecureSkipVerify(insecureSkipTLSVerify), ) if err != nil { diff --git a/pkg/pusher/ocipusher.go b/pkg/pusher/ocipusher.go index 25682969b..f03188391 100644 --- a/pkg/pusher/ocipusher.go +++ b/pkg/pusher/ocipusher.go @@ -109,9 +109,9 @@ func NewOCIPusher(ops ...Option) (Pusher, error) { } func (pusher *OCIPusher) newRegistryClient() (*registry.Client, error) { - if (pusher.opts.certFile != "" && pusher.opts.keyFile != "") || pusher.opts.caFile != "" || pusher.opts.insecureSkipTLSverify { + if (pusher.opts.certFile != "" && pusher.opts.keyFile != "") || pusher.opts.caFile != "" || pusher.opts.insecureSkipTLSVerify { tlsConf, err := tlsutil.NewTLSConfig( - tlsutil.WithInsecureSkipVerify(pusher.opts.insecureSkipTLSverify), + tlsutil.WithInsecureSkipVerify(pusher.opts.insecureSkipTLSVerify), tlsutil.WithCertKeyPairFiles(pusher.opts.certFile, pusher.opts.keyFile), tlsutil.WithCAFile(pusher.opts.caFile), ) diff --git a/pkg/pusher/ocipusher_test.go b/pkg/pusher/ocipusher_test.go index 24f52a7ad..b7d362681 100644 --- a/pkg/pusher/ocipusher_test.go +++ b/pkg/pusher/ocipusher_test.go @@ -40,13 +40,13 @@ func TestNewOCIPusher(t *testing.T) { cd := "../../testdata" join := filepath.Join ca, pub, priv := join(cd, "rootca.crt"), join(cd, "crt.pem"), join(cd, "key.pem") - insecureSkipTLSverify := false + insecureSkipTLSVerify := false plainHTTP := false // Test with options p, err = NewOCIPusher( WithTLSClientConfig(pub, priv, ca), - WithInsecureSkipTLSVerify(insecureSkipTLSverify), + WithInsecureSkipTLSVerify(insecureSkipTLSVerify), WithPlainHTTP(plainHTTP), ) if err != nil { @@ -74,8 +74,8 @@ func TestNewOCIPusher(t *testing.T) { 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) + if op.opts.insecureSkipTLSVerify != insecureSkipTLSVerify { + t.Errorf("Expected NewOCIPusher to have insecureSkipVerifyTLS as %t, got %t", insecureSkipTLSVerify, op.opts.insecureSkipTLSVerify) } // Test if setting registryClient is being passed to the ops @@ -422,7 +422,7 @@ func TestOCIPusher_Push_MultipleOptions(t *testing.T) { if !op.opts.plainHTTP { t.Error("Expected plainHTTP option to be applied") } - if !op.opts.insecureSkipTLSverify { - t.Error("Expected insecureSkipTLSverify option to be applied") + if !op.opts.insecureSkipTLSVerify { + t.Error("Expected insecureSkipTLSVerify option to be applied") } } diff --git a/pkg/pusher/pusher.go b/pkg/pusher/pusher.go index e3c767be9..8ce78b011 100644 --- a/pkg/pusher/pusher.go +++ b/pkg/pusher/pusher.go @@ -32,7 +32,7 @@ type options struct { certFile string keyFile string caFile string - insecureSkipTLSverify bool + insecureSkipTLSVerify bool plainHTTP bool } @@ -59,7 +59,7 @@ func WithTLSClientConfig(certFile, keyFile, caFile string) Option { // WithInsecureSkipTLSVerify determines if a TLS Certificate will be checked func WithInsecureSkipTLSVerify(insecureSkipTLSVerify bool) Option { return func(opts *options) { - opts.insecureSkipTLSverify = insecureSkipTLSVerify + opts.insecureSkipTLSVerify = insecureSkipTLSVerify } } diff --git a/pkg/repo/v1/chartrepo.go b/pkg/repo/v1/chartrepo.go index 95c04ccef..1db913322 100644 --- a/pkg/repo/v1/chartrepo.go +++ b/pkg/repo/v1/chartrepo.go @@ -41,7 +41,7 @@ type Entry struct { CertFile string `json:"certFile"` KeyFile string `json:"keyFile"` CAFile string `json:"caFile"` - InsecureSkipTLSverify bool `json:"insecure_skip_tls_verify"` + InsecureSkipTLSVerify bool `json:"insecure_skip_tls_verify"` PassCredentialsAll bool `json:"pass_credentials_all"` } @@ -82,7 +82,7 @@ func (r *ChartRepository) DownloadIndexFile() (string, error) { resp, err := r.Client.Get(indexURL, getter.WithURL(r.Config.URL), - getter.WithInsecureSkipVerifyTLS(r.Config.InsecureSkipTLSverify), + getter.WithInsecureSkipVerifyTLS(r.Config.InsecureSkipTLSVerify), getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile), getter.WithBasicAuth(r.Config.Username, r.Config.Password), getter.WithPassCredentialsAll(r.Config.PassCredentialsAll), @@ -120,7 +120,7 @@ type findChartInRepoURLOptions struct { Username string Password string PassCredentialsAll bool - InsecureSkipTLSverify bool + InsecureSkipTLSVerify bool CertFile string KeyFile string CAFile string @@ -160,10 +160,10 @@ func WithClientTLS(certFile, keyFile, caFile string) FindChartInRepoURLOption { } } -// WithInsecureSkipTLSverify skips TLS verification for repository communication -func WithInsecureSkipTLSverify(insecureSkipTLSverify bool) FindChartInRepoURLOption { +// WithInsecureSkipTLSVerify skips TLS verification for repository communication +func WithInsecureSkipTLSVerify(insecureSkipTLSVerify bool) FindChartInRepoURLOption { return func(options *findChartInRepoURLOptions) { - options.InsecureSkipTLSverify = insecureSkipTLSverify + options.InsecureSkipTLSVerify = insecureSkipTLSVerify } } @@ -190,7 +190,7 @@ func FindChartInRepoURL(repoURL string, chartName string, getters getter.Provide KeyFile: opts.KeyFile, CAFile: opts.CAFile, Name: name, - InsecureSkipTLSverify: opts.InsecureSkipTLSverify, + InsecureSkipTLSVerify: opts.InsecureSkipTLSVerify, } r, err := NewChartRepository(&c, getters) if err != nil { diff --git a/pkg/repo/v1/chartrepo_test.go b/pkg/repo/v1/chartrepo_test.go index 05e034dd8..e4e76646a 100644 --- a/pkg/repo/v1/chartrepo_test.go +++ b/pkg/repo/v1/chartrepo_test.go @@ -132,7 +132,7 @@ func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) { srv.URL, "nginx", getter.All(&cli.EnvSettings{}), - WithInsecureSkipTLSverify(true), + WithInsecureSkipTLSVerify(true), ) if err != nil { t.Fatalf("%v", err) From 0f5eda74c1a0cf49d3a6e52ad3959ad1cfb2af55 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Thu, 6 Nov 2025 09:06:23 -0500 Subject: [PATCH 017/114] 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 018/114] 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 4576a81a00db475980aebeb5aed2502b2f755017 Mon Sep 17 00:00:00 2001 From: tison Date: Fri, 7 Nov 2025 11:41:19 +0800 Subject: [PATCH 019/114] for remaining local variable case inconsistency Signed-off-by: tison --- pkg/downloader/manager.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 6a0b91cd4..6043fbaaa 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -315,7 +315,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { // Any failure to resolve/download a chart should fail: // https://github.com/helm/helm/issues/1439 - churl, username, password, insecureskiptlsverify, passcredentialsall, caFile, certFile, keyFile, err := m.findChartURL(dep.Name, dep.Version, dep.Repository, repos) + churl, username, password, insecureSkipTLSVerify, passCredentialsAll, caFile, certFile, keyFile, err := m.findChartURL(dep.Name, dep.Version, dep.Repository, repos) if err != nil { saveError = fmt.Errorf("could not find %s: %w", churl, err) break @@ -339,8 +339,8 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { Getters: m.Getters, Options: []getter.Option{ getter.WithBasicAuth(username, password), - getter.WithPassCredentialsAll(passcredentialsall), - getter.WithInsecureSkipVerifyTLS(insecureskiptlsverify), + getter.WithPassCredentialsAll(passCredentialsAll), + getter.WithInsecureSkipVerifyTLS(insecureSkipTLSVerify), getter.WithTLSClientConfig(certFile, keyFile, caFile), }, } @@ -725,7 +725,7 @@ func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { // repoURL is the repository to search // // If it finds a URL that is "relative", it will prepend the repoURL. -func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (url, username, password string, insecureskiptlsverify, passcredentialsall bool, caFile, certFile, keyFile string, err error) { +func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (url, username, password string, insecureSkipTLSVerify, passCredentialsAll bool, caFile, certFile, keyFile string, err error) { if registry.IsOCI(repoURL) { return fmt.Sprintf("%s/%s:%s", repoURL, name, version), "", "", false, false, "", "", "", nil } @@ -754,8 +754,8 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]* } username = cr.Config.Username password = cr.Config.Password - passcredentialsall = cr.Config.PassCredentialsAll - insecureskiptlsverify = cr.Config.InsecureSkipTLSVerify + passCredentialsAll = cr.Config.PassCredentialsAll + insecureSkipTLSVerify = cr.Config.InsecureSkipTLSVerify caFile = cr.Config.CAFile certFile = cr.Config.CertFile keyFile = cr.Config.KeyFile From 2f1ecc7100868be90d302a299d707c70a7f45276 Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Mon, 13 Nov 2023 21:21:43 +0100 Subject: [PATCH 020/114] 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 021/114] 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 caff03fb4ec31bf60009b474020251a63491ce31 Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Sat, 8 Nov 2025 17:05:08 +0200 Subject: [PATCH 022/114] Rename copilot-instructions.md to AGENTS.md As of https://github.blog/changelog/2025-08-28-copilot-coding-agent-now-supports-agents-md-custom-instructions/, AGENTS.md is supported. Signed-off-by: Yarden Shoham --- .github/copilot-instructions.md => AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/copilot-instructions.md => AGENTS.md (97%) diff --git a/.github/copilot-instructions.md b/AGENTS.md similarity index 97% rename from .github/copilot-instructions.md rename to AGENTS.md index 5232bbc82..d2904a9da 100644 --- a/.github/copilot-instructions.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ -# Copilot Instructions for Helm +# AGENTS.md ## Overview Helm is a package manager for Kubernetes written in Go, supporting v3 (stable) and v4 (unstable) APIs. From 6de83c5f60d4b7c463ed1f1d864381f420cee457 Mon Sep 17 00:00:00 2001 From: saimanojk1 Date: Sat, 8 Nov 2025 19:48:37 +0100 Subject: [PATCH 023/114] Convert pkg/cmd/load_plugins.go to slog Signed-off-by: saimanojk1 --- pkg/cmd/load_plugins.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/pkg/cmd/load_plugins.go b/pkg/cmd/load_plugins.go index 534113bde..ddda5fde6 100644 --- a/pkg/cmd/load_plugins.go +++ b/pkg/cmd/load_plugins.go @@ -20,7 +20,7 @@ import ( "context" "fmt" "io" - "log" + "log/slog" "os" "path/filepath" "slices" @@ -63,7 +63,7 @@ func loadCLIPlugins(baseCmd *cobra.Command, out io.Writer) { } found, err := plugin.FindPlugins(dirs, descriptor) if err != nil { - fmt.Fprintf(os.Stderr, "failed to load plugins: %s\n", err) + slog.Error("failed to load plugins", slog.String("error", err.Error())) return } @@ -217,9 +217,7 @@ func loadCompletionForPlugin(pluginCmd *cobra.Command, plug plugin.Plugin) { if err != nil { // The file could be missing or invalid. No static completion for this plugin. - if settings.Debug { - log.Output(2, fmt.Sprintf("[info] %s\n", err.Error())) - } + slog.Debug("plugin completion file loading", slog.String("error", err.Error())) // Continue to setup dynamic completion. cmds = &pluginCommand{} } @@ -238,10 +236,7 @@ func addPluginCommands(plug plugin.Plugin, baseCmd *cobra.Command, cmds *pluginC } if len(cmds.Name) == 0 { - // Missing name for a command - if settings.Debug { - log.Output(2, fmt.Sprintf("[info] sub-command name field missing for %s", baseCmd.CommandPath())) - } + slog.Debug("sub-command name field missing", slog.String("commandPath", baseCmd.CommandPath())) return } From d93ef03ee400292452dec97e88e821dc3188110e Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Mon, 10 Nov 2025 10:25:16 -0600 Subject: [PATCH 024/114] fix: do not run release workflow on forks Signed-off-by: Terry Howe --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 21c527442..8649db959 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ permissions: read-all # job is triggered by a tag push, VERSION should be the tag ref. jobs: release: - if: startsWith(github.ref, 'refs/tags/v') + if: startsWith(github.ref, 'refs/tags/v') && github.repository == 'helm/helm' runs-on: ubuntu-latest-16-cores steps: - name: Checkout source code @@ -76,7 +76,7 @@ jobs: canary-release: runs-on: ubuntu-latest-16-cores - if: github.ref == 'refs/heads/main' + if: github.ref == 'refs/heads/main' && github.repository == 'helm/helm' steps: - name: Checkout source code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0 From 5cbd9b3035cee869566491c686c1f90ce1b52171 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Fri, 24 Oct 2025 18:34:28 +0200 Subject: [PATCH 025/114] While testing SDK features for v4. I was surprised with the error: > "reporter failed to start: event funnel closed: context deadline exceeded" It happens when you forget to set a minimal timeout: ```go upgradeClient := action.NewUpgrade(actionConfig) upgradeClient.WaitStrategy = kube.StatusWatcherStrategy // When Timeout is zero, the status wait uses a context with zero timeout which // immediately expires causing "context deadline exceeded" errors. upgradeClient.Timeout = 2 * time.Minute ``` Also maybe it might be worth documenting this more clearly. Initial [PR](https://github.com/helm/helm/pull/13604) say: > I have not written any docs, I assume that can be done when we are closer to Helm 4, a lot of it is covered by linking to - https://github.com/kubernetes-sigs/cli-utils/blob/master/pkg/kstatus/README.md Related: - https://github.com/helm/helm/pull/31411#issuecomment-3443925663 Signed-off-by: Benoit Tigeot --- pkg/kube/client.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 68f1e6475..56de52651 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -98,12 +98,23 @@ type Client struct { var _ Interface = (*Client)(nil) +// WaitStrategy represents the algorithm used to wait for Kubernetes +// resources to reach their desired state. type WaitStrategy string const ( + // StatusWatcherStrategy: event-driven waits using kstatus (watches + aggregated readers). + // Default for --wait. More accurate and responsive; waits CRs and full reconciliation. + // Requires: reachable API server, list+watch RBAC on deployed resources, and a non-zero timeout. StatusWatcherStrategy WaitStrategy = "watcher" - LegacyStrategy WaitStrategy = "legacy" - HookOnlyStrategy WaitStrategy = "hookOnly" + + // LegacyStrategy: Helm 3-style periodic polling until ready or timeout. + // Use when watches aren’t available/reliable, or for compatibility/simple CI. + // Requires only list RBAC for polled resources. + LegacyStrategy WaitStrategy = "legacy" + + // HookOnlyStrategy: wait only for hook Pods/Jobs to complete; does not wait for general chart resources. + HookOnlyStrategy WaitStrategy = "hookOnly" ) type FieldValidationDirective string From 95e1ee108936f15657f644e2e87f0b4c8319edb2 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Fri, 24 Oct 2025 18:54:59 +0200 Subject: [PATCH 026/114] Increase documentation of --wait flag Signed-off-by: Benoit Tigeot --- 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 b20772ef9..941602211 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, will wait until all resources are in the expected state before marking the operation as successful. It will wait for as long as --timeout. Valid inputs are 'watcher' and 'legacy'", + "wait until resources are ready (up to --timeout). Values: 'watcher' (default), 'legacy'. '--wait' without value = 'watcher'. Booleans deprecated; '--wait=false' does hook-only.", ) // Sets the strategy to use the watcher strategy if `--wait` is used without an argument cmd.Flags().Lookup("wait").NoOptDefVal = string(kube.StatusWatcherStrategy) From e8b0cff45a21de6fe144789830e46c188e713313 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Sat, 25 Oct 2025 11:15:06 +0200 Subject: [PATCH 027/114] Update pkg/cmd/flags.go Co-authored-by: George Jenkins Signed-off-by: Benoit Tigeot Signed-off-by: Benoit Tigeot --- 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 941602211..4f416ddf0 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", - "wait until resources are ready (up to --timeout). Values: 'watcher' (default), 'legacy'. '--wait' without value = 'watcher'. Booleans deprecated; '--wait=false' does hook-only.", + "wait until resources are ready (up to --timeout). Values: 'watcher' (default), 'legacy', and '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 a5e110fccd9b8ce6f0dc110d2c83ee079f15e8e4 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Sat, 25 Oct 2025 13:25:22 +0200 Subject: [PATCH 028/114] Make wait strategy selection more obvious It is not clear enough that you need to set --wait=false for a hook only strategy. Signed-off-by: Benoit Tigeot --- pkg/cmd/flags.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index 4f416ddf0..1aef6430a 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -81,7 +81,7 @@ func (ws *waitValue) String() string { func (ws *waitValue) Set(s string) error { switch s { - case string(kube.StatusWatcherStrategy), string(kube.LegacyStrategy): + case string(kube.StatusWatcherStrategy), string(kube.LegacyStrategy), string(kube.HookOnlyStrategy): *ws = waitValue(s) return nil case "true": @@ -93,7 +93,7 @@ func (ws *waitValue) Set(s string) error { *ws = waitValue(kube.HookOnlyStrategy) return nil default: - return fmt.Errorf("invalid wait input %q. Valid inputs are %s, and %s", s, kube.StatusWatcherStrategy, kube.LegacyStrategy) + return fmt.Errorf("invalid wait input %q. Valid inputs are %s, %s, and %s", s, kube.StatusWatcherStrategy, kube.LegacyStrategy, kube.HookOnlyStrategy) } } From 1836f37f4d5a59f8e8fa9b78bb2517e6069292ac Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Sat, 25 Oct 2025 13:27:35 +0200 Subject: [PATCH 029/114] The default is not HookOnlyStrategy but WaitStrategy Signed-off-by: Benoit Tigeot --- 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 1aef6430a..55633e62b 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -89,7 +89,7 @@ func (ws *waitValue) Set(s string) error { *ws = waitValue(kube.StatusWatcherStrategy) return nil case "false": - slog.Warn("--wait=false is deprecated (boolean value) and can be replaced by omitting the --wait flag") + slog.Warn("--wait=false is deprecated (boolean value) and can be replaced with --wait=hookOnly") *ws = waitValue(kube.HookOnlyStrategy) return nil default: From 8535e9f4ab5c143fcbd429f45a0f15cf510a0cc0 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Sat, 25 Oct 2025 13:34:31 +0200 Subject: [PATCH 030/114] Avoid confusion between `--wait` (watcher) and no --wait (hookOnly) Signed-off-by: Benoit Tigeot --- pkg/cmd/flags.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index 55633e62b..ee4806280 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -57,9 +57,9 @@ func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) { func AddWaitFlag(cmd *cobra.Command, wait *kube.WaitStrategy) { cmd.Flags().Var( - newWaitValue(kube.HookOnlyStrategy, wait), + newWaitValue(kube.StatusWatcherStrategy, wait), "wait", - "wait until resources are ready (up to --timeout). Values: 'watcher' (default), 'legacy', and 'hookOnly'", + "wait until resources are ready (up to --timeout). Values: 'watcher' (default), '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 11128659aa76b09aae2e7903c7790dabbe7a4c95 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Mon, 27 Oct 2025 10:50:12 +0100 Subject: [PATCH 031/114] Provide more help for SDK user when setting up WaitStrategy Signed-off-by: Benoit Tigeot --- pkg/kube/client.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 56de52651..a660d7075 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -176,8 +176,10 @@ func (c *Client) GetWaiter(strategy WaitStrategy) (Waiter, error) { return nil, err } return &hookOnlyWaiter{sw: sw}, nil + case "": + return nil, errors.New("wait strategy not set. Choose one of: " + string(StatusWatcherStrategy) + ", " + string(HookOnlyStrategy) + ", " + string(LegacyStrategy)) default: - return nil, errors.New("unknown wait strategy") + return nil, errors.New("unknown wait strategy (s" + string(strategy) + "). Valid values are: " + string(StatusWatcherStrategy) + ", " + string(HookOnlyStrategy) + ", " + string(LegacyStrategy)) } } From 52a282832fad5691bbcfb28cf8c3dfade914a597 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Mon, 27 Oct 2025 14:58:40 +0100 Subject: [PATCH 032/114] Do not change the default waiting strategy when --wait is not set It is too much a breaking change for a new RC of beta V4. See comment: https://github.com/helm/helm/pull/31421#discussion_r2465121438 Signed-off-by: Benoit Tigeot --- pkg/cmd/flags.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index ee4806280..5b8b8cf21 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -57,9 +57,9 @@ func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) { func AddWaitFlag(cmd *cobra.Command, wait *kube.WaitStrategy) { cmd.Flags().Var( - newWaitValue(kube.StatusWatcherStrategy, wait), + newWaitValue(kube.HookOnlyStrategy, wait), "wait", - "wait until resources are ready (up to --timeout). Values: 'watcher' (default), 'hookOnly', and 'legacy'", + "wait until resources are ready (up to --timeout). Values: 'watcher', 'hookOnly' (default), 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 5f6fa437b2c2954092b6c3f8af5b83aef7dcb874 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Mon, 27 Oct 2025 15:19:09 +0100 Subject: [PATCH 033/114] Prevent surprising failure with SDK when timeout is not set While testing SDK features for v4. I was surprised with the error: "reporter failed to start: event funnel closed: context deadline exceeded" This occurs when no timeout is set: ``` upgradeClient := action.NewUpgrade(actionConfig) upgradeClient.WaitStrategy = kube.StatusWatcherStrategy // When Timeout is zero, the status wait uses a context with zero timeout which // immediately expires causing "context deadline exceeded" errors. upgradeClient.Timeout = 2 * time.Minute ``` With this patch it will work without specifying. Signed-off-by: Benoit Tigeot --- pkg/kube/statuswait.go | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index cd9722eda..31d4f4444 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -47,6 +47,13 @@ type statusWaiter struct { ctx context.Context } +// DefaultStatusWatcherTimeout is the timeout used by the status waiter when a +// zero timeout is provided. This prevents callers from accidentally passing a +// zero value (which would immediately cancel the context) and getting +// "context deadline exceeded" errors. SDK callers can rely on this default +// when they don't set a timeout. +var DefaultStatusWatcherTimeout = 30 * time.Second + func alwaysReady(_ *unstructured.Unstructured) (*status.Result, error) { return &status.Result{ Status: status.CurrentStatus, @@ -55,7 +62,10 @@ func alwaysReady(_ *unstructured.Unstructured) (*status.Result, error) { } func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.Duration) error { - ctx, cancel := w.contextWithTimeout(timeout) + if timeout == 0 { + timeout = DefaultStatusWatcherTimeout + } + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) @@ -76,7 +86,16 @@ func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.D } func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) error { +<<<<<<< HEAD ctx, cancel := w.contextWithTimeout(timeout) +||||||| parent of 86f98f870 (Prevent surprising failure with SDK when timeout is not set) + ctx, cancel := context.WithTimeout(context.TODO(), timeout) +======= + if timeout == 0 { + timeout = DefaultStatusWatcherTimeout + } + ctx, cancel := context.WithTimeout(context.TODO(), timeout) +>>>>>>> 86f98f870 (Prevent surprising failure with SDK when timeout is not set) defer cancel() slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) @@ -84,7 +103,16 @@ func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) er } func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Duration) error { +<<<<<<< HEAD ctx, cancel := w.contextWithTimeout(timeout) +||||||| parent of 86f98f870 (Prevent surprising failure with SDK when timeout is not set) + ctx, cancel := context.WithTimeout(context.TODO(), timeout) +======= + if timeout == 0 { + timeout = DefaultStatusWatcherTimeout + } + ctx, cancel := context.WithTimeout(context.TODO(), timeout) +>>>>>>> 86f98f870 (Prevent surprising failure with SDK when timeout is not set) defer cancel() slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) @@ -95,7 +123,16 @@ func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Dura } func (w *statusWaiter) WaitForDelete(resourceList ResourceList, timeout time.Duration) error { +<<<<<<< HEAD ctx, cancel := w.contextWithTimeout(timeout) +||||||| parent of 86f98f870 (Prevent surprising failure with SDK when timeout is not set) + ctx, cancel := context.WithTimeout(context.TODO(), timeout) +======= + if timeout == 0 { + timeout = DefaultStatusWatcherTimeout + } + ctx, cancel := context.WithTimeout(context.TODO(), timeout) +>>>>>>> 86f98f870 (Prevent surprising failure with SDK when timeout is not set) defer cancel() slog.Debug("waiting for resources to be deleted", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) From 277c140c871ed112da916d8997c7d243e39f41b3 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Mon, 27 Oct 2025 15:39:13 +0100 Subject: [PATCH 034/114] Error strategy list match help Signed-off-by: Benoit Tigeot --- 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 5b8b8cf21..3269720a1 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -93,7 +93,7 @@ func (ws *waitValue) Set(s string) error { *ws = waitValue(kube.HookOnlyStrategy) return nil default: - return fmt.Errorf("invalid wait input %q. Valid inputs are %s, %s, and %s", s, kube.StatusWatcherStrategy, kube.LegacyStrategy, kube.HookOnlyStrategy) + return fmt.Errorf("invalid wait input %q. Valid inputs are %s, %s, and %s", s, kube.StatusWatcherStrategy, kube.HookOnlyStrategy, kube.LegacyStrategy) } } From 02312a1cf2124abc27e2cd261d8df8dcc0c6f1e4 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Wed, 29 Oct 2025 23:00:36 +0100 Subject: [PATCH 035/114] Update pkg/cmd/flags.go Co-authored-by: George Jenkins Signed-off-by: Benoit Tigeot Signed-off-by: Benoit Tigeot --- pkg/cmd/flags.go | 2 +- pkg/kube/statuswait.go | 26 ++++---------------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index 3269720a1..efe0be6e7 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", - "wait until resources are ready (up to --timeout). Values: 'watcher', 'hookOnly' (default), and 'legacy'", + "if specified, wait until resources are ready (up to --timeout). Values: 'watcher' (default), '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) diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index 31d4f4444..225321f6e 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -65,7 +65,7 @@ func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.D if timeout == 0 { timeout = DefaultStatusWatcherTimeout } - ctx, cancel := context.WithTimeout(context.Background(), timeout) + ctx, cancel := w.contextWithTimeout(timeout) defer cancel() slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) @@ -86,16 +86,10 @@ func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.D } func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) error { -<<<<<<< HEAD - ctx, cancel := w.contextWithTimeout(timeout) -||||||| parent of 86f98f870 (Prevent surprising failure with SDK when timeout is not set) - ctx, cancel := context.WithTimeout(context.TODO(), timeout) -======= if timeout == 0 { timeout = DefaultStatusWatcherTimeout } - ctx, cancel := context.WithTimeout(context.TODO(), timeout) ->>>>>>> 86f98f870 (Prevent surprising failure with SDK when timeout is not set) + ctx, cancel := w.contextWithTimeout(timeout) defer cancel() slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) @@ -103,16 +97,10 @@ func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) er } func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Duration) error { -<<<<<<< HEAD - ctx, cancel := w.contextWithTimeout(timeout) -||||||| parent of 86f98f870 (Prevent surprising failure with SDK when timeout is not set) - ctx, cancel := context.WithTimeout(context.TODO(), timeout) -======= if timeout == 0 { timeout = DefaultStatusWatcherTimeout } - ctx, cancel := context.WithTimeout(context.TODO(), timeout) ->>>>>>> 86f98f870 (Prevent surprising failure with SDK when timeout is not set) + ctx, cancel := w.contextWithTimeout(timeout) defer cancel() slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) @@ -123,16 +111,10 @@ func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Dura } func (w *statusWaiter) WaitForDelete(resourceList ResourceList, timeout time.Duration) error { -<<<<<<< HEAD - ctx, cancel := w.contextWithTimeout(timeout) -||||||| parent of 86f98f870 (Prevent surprising failure with SDK when timeout is not set) - ctx, cancel := context.WithTimeout(context.TODO(), timeout) -======= if timeout == 0 { timeout = DefaultStatusWatcherTimeout } - ctx, cancel := context.WithTimeout(context.TODO(), timeout) ->>>>>>> 86f98f870 (Prevent surprising failure with SDK when timeout is not set) + ctx, cancel := w.contextWithTimeout(timeout) defer cancel() slog.Debug("waiting for resources to be deleted", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) From 7a8535810acba53b4cf4ad65af040c58b37a5242 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 21:19:02 +0000 Subject: [PATCH 036/114] chore(deps): bump golangci/golangci-lint-action from 8.0.0 to 9.0.0 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 8.0.0 to 9.0.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/4afd733a84b1f43292c63897423277bb7f4313a9...0a35821d5c230e903fcfe077583637dea1b27b47) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major ... 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 0d5b4e969..ff441aad5 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@4afd733a84b1f43292c63897423277bb7f4313a9 #pin@8.0.0 + uses: golangci/golangci-lint-action@0a35821d5c230e903fcfe077583637dea1b27b47 #pin@9.0.0 with: version: ${{ env.GOLANGCI_LINT_VERSION }} From 0089a07bb855e2dc8169d5426bf22d33547b38ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 21:34:05 +0000 Subject: [PATCH 037/114] chore(deps): bump sigs.k8s.io/kustomize/kyaml from 0.20.1 to 0.21.0 Bumps [sigs.k8s.io/kustomize/kyaml](https://github.com/kubernetes-sigs/kustomize) from 0.20.1 to 0.21.0. - [Release notes](https://github.com/kubernetes-sigs/kustomize/releases) - [Commits](https://github.com/kubernetes-sigs/kustomize/compare/api/v0.20.1...api/v0.21.0) --- updated-dependencies: - dependency-name: sigs.k8s.io/kustomize/kyaml dependency-version: 0.21.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index e4226760c..23bc1ae5c 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( k8s.io/kubectl v0.34.1 oras.land/oras-go/v2 v2.6.0 sigs.k8s.io/controller-runtime v0.22.4 - sigs.k8s.io/kustomize/kyaml v0.20.1 + sigs.k8s.io/kustomize/kyaml v0.21.0 sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index a43d215cd..dd8ef3f86 100644 --- a/go.sum +++ b/go.sum @@ -287,16 +287,16 @@ github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnA github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +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/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/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +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= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -542,8 +542,8 @@ sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7np sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/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.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= -sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= +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/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= From d8c40403364c817315ca9de85ba5e371905e5d03 Mon Sep 17 00:00:00 2001 From: promalert Date: Tue, 11 Nov 2025 13:46:49 +0800 Subject: [PATCH 038/114] refactor: use strings.Builder to improve performance Signed-off-by: promalert --- pkg/action/uninstall.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index 0cb31be43..d5474490c 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -242,9 +242,9 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri } filesToKeep, filesToDelete := filterManifestsToKeep(files) - var kept string + var kept strings.Builder for _, f := range filesToKeep { - kept += "[" + f.Head.Kind + "] " + f.Head.Metadata.Name + "\n" + fmt.Fprintf(&kept, "[%s] %s\n", f.Head.Kind, f.Head.Metadata.Name) } var builder strings.Builder @@ -259,7 +259,7 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri if len(resources) > 0 { _, errs = u.cfg.KubeClient.Delete(resources, parseCascadingFlag(u.DeletionPropagation)) } - return resources, kept, errs + return resources, kept.String(), errs } func parseCascadingFlag(cascadingFlag string) v1.DeletionPropagation { From 6cd0bf81713d20a1ba609d7b6d10816410a1ebd4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 21:12:34 +0000 Subject: [PATCH 039/114] chore(deps): bump golang.org/x/term from 0.36.0 to 0.37.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.36.0 to 0.37.0. - [Commits](https://github.com/golang/term/compare/v0.36.0...v0.37.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-version: 0.37.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 e4226760c..690e939d8 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/tetratelabs/wazero v1.9.0 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.43.0 - golang.org/x/term v0.36.0 + golang.org/x/term v0.37.0 golang.org/x/text v0.30.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.34.1 @@ -166,7 +166,7 @@ require ( golang.org/x/net v0.45.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.37.0 // indirect + golang.org/x/sys v0.38.0 // indirect golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.37.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect diff --git a/go.sum b/go.sum index a43d215cd..917be50d5 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.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +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/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.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= -golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +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/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 5ae85868d45ca7bb9ac3ef7a10e0db54b8a8695c Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Wed, 12 Nov 2025 08:33:39 -0500 Subject: [PATCH 040/114] Adding script to download Helm v4 Signed-off-by: Matt Farina --- scripts/get-helm-4 | 347 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 scripts/get-helm-4 diff --git a/scripts/get-helm-4 b/scripts/get-helm-4 new file mode 100644 index 000000000..1c90bbad5 --- /dev/null +++ b/scripts/get-helm-4 @@ -0,0 +1,347 @@ +#!/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. + +# The install script is based off of the MIT-licensed script from glide, +# the package manager for Go: https://github.com/Masterminds/glide.sh/blob/master/get + +: ${BINARY_NAME:="helm"} +: ${USE_SUDO:="true"} +: ${DEBUG:="false"} +: ${VERIFY_CHECKSUM:="true"} +: ${VERIFY_SIGNATURES:="false"} +: ${HELM_INSTALL_DIR:="/usr/local/bin"} +: ${GPG_PUBRING:="pubring.kbx"} + +HAS_CURL="$(type "curl" &> /dev/null && echo true || echo false)" +HAS_WGET="$(type "wget" &> /dev/null && echo true || echo false)" +HAS_OPENSSL="$(type "openssl" &> /dev/null && echo true || echo false)" +HAS_GPG="$(type "gpg" &> /dev/null && echo true || echo false)" +HAS_GIT="$(type "git" &> /dev/null && echo true || echo false)" +HAS_TAR="$(type "tar" &> /dev/null && echo true || echo false)" + +# initArch discovers the architecture for this system. +initArch() { + ARCH=$(uname -m) + case $ARCH in + armv5*) ARCH="armv5";; + armv6*) ARCH="armv6";; + armv7*) ARCH="arm";; + aarch64) ARCH="arm64";; + x86) ARCH="386";; + x86_64) ARCH="amd64";; + i686) ARCH="386";; + i386) ARCH="386";; + esac +} + +# initOS discovers the operating system for this system. +initOS() { + OS=$(echo `uname`|tr '[:upper:]' '[:lower:]') + + case "$OS" in + # Minimalist GNU for Windows + mingw*|cygwin*) OS='windows';; + esac +} + +# runs the given command as root (detects if we are root already) +runAsRoot() { + if [ $EUID -ne 0 -a "$USE_SUDO" = "true" ]; then + sudo "${@}" + else + "${@}" + fi +} + +# verifySupported checks that the os/arch combination is supported for +# binary builds, as well whether or not necessary tools are present. +verifySupported() { + local supported="darwin-amd64\ndarwin-arm64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-loong64\nlinux-ppc64le\nlinux-s390x\nlinux-riscv64\nwindows-amd64\nwindows-arm64" + if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then + echo "No prebuilt binary for ${OS}-${ARCH}." + echo "To build from source, go to https://github.com/helm/helm" + exit 1 + fi + + if [ "${HAS_CURL}" != "true" ] && [ "${HAS_WGET}" != "true" ]; then + echo "Either curl or wget is required" + exit 1 + fi + + if [ "${VERIFY_CHECKSUM}" == "true" ] && [ "${HAS_OPENSSL}" != "true" ]; then + echo "In order to verify checksum, openssl must first be installed." + echo "Please install openssl or set VERIFY_CHECKSUM=false in your environment." + exit 1 + fi + + if [ "${VERIFY_SIGNATURES}" == "true" ]; then + if [ "${HAS_GPG}" != "true" ]; then + echo "In order to verify signatures, gpg must first be installed." + echo "Please install gpg or set VERIFY_SIGNATURES=false in your environment." + exit 1 + fi + if [ "${OS}" != "linux" ]; then + echo "Signature verification is currently only supported on Linux." + echo "Please set VERIFY_SIGNATURES=false or verify the signatures manually." + exit 1 + fi + fi + + if [ "${HAS_GIT}" != "true" ]; then + echo "[WARNING] Could not find git. It is required for plugin installation." + fi + + if [ "${HAS_TAR}" != "true" ]; then + echo "[ERROR] Could not find tar. It is required to extract the helm binary archive." + exit 1 + fi +} + +# checkDesiredVersion checks if the desired version is available. +checkDesiredVersion() { + if [ "x$DESIRED_VERSION" == "x" ]; then + # Get tag from release URL + local latest_release_url="https://get.helm.sh/helm4-latest-version" + local latest_release_response="" + if [ "${HAS_CURL}" == "true" ]; then + latest_release_response=$( curl -L --silent --show-error --fail "$latest_release_url" 2>&1 || true ) + elif [ "${HAS_WGET}" == "true" ]; then + latest_release_response=$( wget "$latest_release_url" -q -O - 2>&1 || true ) + fi + TAG=$( echo "$latest_release_response" | grep '^v[0-9]' ) + if [ "x$TAG" == "x" ]; then + printf "Could not retrieve the latest release tag information from %s: %s\n" "${latest_release_url}" "${latest_release_response}" + exit 1 + fi + else + TAG=$DESIRED_VERSION + fi +} + +# checkHelmInstalledVersion checks which version of helm is installed and +# if it needs to be changed. +checkHelmInstalledVersion() { + if [[ -f "${HELM_INSTALL_DIR}/${BINARY_NAME}" ]]; then + local version=$("${HELM_INSTALL_DIR}/${BINARY_NAME}" version --template="{{ .Version }}") + if [[ "$version" == "$TAG" ]]; then + echo "Helm ${version} is already ${DESIRED_VERSION:-latest}" + return 0 + else + echo "Helm ${TAG} is available. Changing from version ${version}." + return 1 + fi + else + return 1 + fi +} + +# downloadFile downloads the latest binary package and also the checksum +# for that binary. +downloadFile() { + HELM_DIST="helm-$TAG-$OS-$ARCH.tar.gz" + DOWNLOAD_URL="https://get.helm.sh/$HELM_DIST" + CHECKSUM_URL="$DOWNLOAD_URL.sha256" + HELM_TMP_ROOT="$(mktemp -dt helm-installer-XXXXXX)" + HELM_TMP_FILE="$HELM_TMP_ROOT/$HELM_DIST" + HELM_SUM_FILE="$HELM_TMP_ROOT/$HELM_DIST.sha256" + echo "Downloading $DOWNLOAD_URL" + if [ "${HAS_CURL}" == "true" ]; then + curl -SsL "$CHECKSUM_URL" -o "$HELM_SUM_FILE" + curl -SsL "$DOWNLOAD_URL" -o "$HELM_TMP_FILE" + elif [ "${HAS_WGET}" == "true" ]; then + wget -q -O "$HELM_SUM_FILE" "$CHECKSUM_URL" + wget -q -O "$HELM_TMP_FILE" "$DOWNLOAD_URL" + fi +} + +# verifyFile verifies the SHA256 checksum of the binary package +# and the GPG signatures for both the package and checksum file +# (depending on settings in environment). +verifyFile() { + if [ "${VERIFY_CHECKSUM}" == "true" ]; then + verifyChecksum + fi + if [ "${VERIFY_SIGNATURES}" == "true" ]; then + verifySignatures + fi +} + +# installFile installs the Helm binary. +installFile() { + HELM_TMP="$HELM_TMP_ROOT/$BINARY_NAME" + mkdir -p "$HELM_TMP" + tar xf "$HELM_TMP_FILE" -C "$HELM_TMP" + HELM_TMP_BIN="$HELM_TMP/$OS-$ARCH/helm" + echo "Preparing to install $BINARY_NAME into ${HELM_INSTALL_DIR}" + runAsRoot cp "$HELM_TMP_BIN" "$HELM_INSTALL_DIR/$BINARY_NAME" + echo "$BINARY_NAME installed into $HELM_INSTALL_DIR/$BINARY_NAME" +} + +# verifyChecksum verifies the SHA256 checksum of the binary package. +verifyChecksum() { + printf "Verifying checksum... " + local sum=$(openssl sha1 -sha256 ${HELM_TMP_FILE} | awk '{print $2}') + local expected_sum=$(cat ${HELM_SUM_FILE}) + if [ "$sum" != "$expected_sum" ]; then + echo "SHA sum of ${HELM_TMP_FILE} does not match. Aborting." + exit 1 + fi + echo "Done." +} + +# verifySignatures obtains the latest KEYS file from GitHub main branch +# as well as the signature .asc files from the specific GitHub release, +# then verifies that the release artifacts were signed by a maintainer's key. +verifySignatures() { + printf "Verifying signatures... " + local keys_filename="KEYS" + local github_keys_url="https://raw.githubusercontent.com/helm/helm/main/${keys_filename}" + if [ "${HAS_CURL}" == "true" ]; then + curl -SsL "${github_keys_url}" -o "${HELM_TMP_ROOT}/${keys_filename}" + elif [ "${HAS_WGET}" == "true" ]; then + wget -q -O "${HELM_TMP_ROOT}/${keys_filename}" "${github_keys_url}" + fi + local gpg_keyring="${HELM_TMP_ROOT}/keyring.gpg" + local gpg_homedir="${HELM_TMP_ROOT}/gnupg" + mkdir -p -m 0700 "${gpg_homedir}" + local gpg_stderr_device="/dev/null" + if [ "${DEBUG}" == "true" ]; then + gpg_stderr_device="/dev/stderr" + fi + gpg --batch --quiet --homedir="${gpg_homedir}" --import "${HELM_TMP_ROOT}/${keys_filename}" 2> "${gpg_stderr_device}" + gpg --batch --no-default-keyring --keyring "${gpg_homedir}/${GPG_PUBRING}" --export > "${gpg_keyring}" + local github_release_url="https://github.com/helm/helm/releases/download/${TAG}" + if [ "${HAS_CURL}" == "true" ]; then + curl -SsL "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" -o "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" + curl -SsL "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" -o "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" + elif [ "${HAS_WGET}" == "true" ]; then + wget -q -O "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" + wget -q -O "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" + fi + local error_text="If you think this might be a potential security issue," + error_text="${error_text}\nplease see here: https://github.com/helm/community/blob/master/SECURITY.md" + local num_goodlines_sha=$(gpg --verify --keyring="${gpg_keyring}" --status-fd=1 "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" 2> "${gpg_stderr_device}" | grep -c -E '^\[GNUPG:\] (GOODSIG|VALIDSIG)') + if [[ ${num_goodlines_sha} -lt 2 ]]; then + echo "Unable to verify the signature of helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256!" + echo -e "${error_text}" + exit 1 + fi + local num_goodlines_tar=$(gpg --verify --keyring="${gpg_keyring}" --status-fd=1 "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" 2> "${gpg_stderr_device}" | grep -c -E '^\[GNUPG:\] (GOODSIG|VALIDSIG)') + if [[ ${num_goodlines_tar} -lt 2 ]]; then + echo "Unable to verify the signature of helm-${TAG}-${OS}-${ARCH}.tar.gz!" + echo -e "${error_text}" + exit 1 + fi + echo "Done." +} + +# fail_trap is executed if an error occurs. +fail_trap() { + result=$? + if [ "$result" != "0" ]; then + if [[ -n "$INPUT_ARGUMENTS" ]]; then + echo "Failed to install $BINARY_NAME with the arguments provided: $INPUT_ARGUMENTS" + help + else + echo "Failed to install $BINARY_NAME" + fi + echo -e "\tFor support, go to https://github.com/helm/helm." + fi + cleanup + exit $result +} + +# testVersion tests the installed client to make sure it is working. +testVersion() { + set +e + HELM="$(command -v $BINARY_NAME)" + if [ "$?" = "1" ]; then + echo "$BINARY_NAME not found. Is $HELM_INSTALL_DIR on your "'$PATH?' + exit 1 + fi + set -e +} + +# help provides possible cli installation arguments +help () { + echo "Accepted cli arguments are:" + echo -e "\t[--help|-h ] ->> prints this help" + echo -e "\t[--version|-v ] . When not defined it fetches the latest release tag from the Helm CDN" + echo -e "\te.g. --version v4.0.0 or -v canary" + echo -e "\t[--no-sudo] ->> install without sudo" +} + +# cleanup temporary files to avoid https://github.com/helm/helm/issues/2977 +cleanup() { + if [[ -d "${HELM_TMP_ROOT:-}" ]]; then + rm -rf "$HELM_TMP_ROOT" + fi +} + +# Execution + +#Stop execution on any error +trap "fail_trap" EXIT +set -e + +# Set debug if desired +if [ "${DEBUG}" == "true" ]; then + set -x +fi + +# Parsing input arguments (if any) +export INPUT_ARGUMENTS="${@}" +set -u +while [[ $# -gt 0 ]]; do + case $1 in + '--version'|-v) + shift + if [[ $# -ne 0 ]]; then + export DESIRED_VERSION="${1}" + if [[ "$1" != "v"* ]]; then + echo "Expected version arg ('${DESIRED_VERSION}') to begin with 'v', fixing..." + export DESIRED_VERSION="v${1}" + fi + else + echo -e "Please provide the desired version. e.g. --version v4.0.0 or -v canary" + exit 0 + fi + ;; + '--no-sudo') + USE_SUDO="false" + ;; + '--help'|-h) + help + exit 0 + ;; + *) exit 1 + ;; + esac + shift +done +set +u + +initArch +initOS +verifySupported +checkDesiredVersion +if ! checkHelmInstalledVersion; then + downloadFile + verifyFile + installFile +fi +testVersion +cleanup From e4353dc8dba01062851a26a8d1227b0c42854f11 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Wed, 12 Nov 2025 09:08:30 -0500 Subject: [PATCH 041/114] Publish Helm v4 -> `helm-latest-version` Signed-off-by: George Jenkins --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 57f7e9167..2e4a8bf1e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,6 +53,8 @@ jobs: echo "Error: Latest version ${LATEST_VERSION} is not a v4 release" exit 1 fi + + echo "${LATEST_VERSION}" > _dist_versions/helm-latest-version echo "${LATEST_VERSION}" > _dist_versions/helm4-latest-version - name: Upload Binaries From 72a84fb369d86eaae2b6b1d4aa0cee74cbc1ee03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 14:17:07 +0000 Subject: [PATCH 042/114] chore(deps): bump github.com/tetratelabs/wazero from 1.9.0 to 1.10.1 Bumps [github.com/tetratelabs/wazero](https://github.com/tetratelabs/wazero) from 1.9.0 to 1.10.1. - [Release notes](https://github.com/tetratelabs/wazero/releases) - [Commits](https://github.com/tetratelabs/wazero/compare/v1.9.0...v1.10.1) --- updated-dependencies: - dependency-name: github.com/tetratelabs/wazero dependency-version: 1.10.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 9ebe216a2..3d251758f 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 - github.com/tetratelabs/wazero v1.9.0 + github.com/tetratelabs/wazero v1.10.1 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.43.0 golang.org/x/term v0.37.0 diff --git a/go.sum b/go.sum index 89e01bebe..32dbbbb70 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.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= -github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= +github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8= +github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU= 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 fb82e0e513f5efd94fa4f7ad6e916ccf51228c33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:45:59 +0000 Subject: [PATCH 043/114] chore(deps): bump golang.org/x/crypto from 0.43.0 to 0.44.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.43.0 to 0.44.0. - [Commits](https://github.com/golang/crypto/compare/v0.43.0...v0.44.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.44.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 3d251758f..29520a019 100644 --- a/go.mod +++ b/go.mod @@ -36,9 +36,9 @@ 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.43.0 + golang.org/x/crypto v0.44.0 golang.org/x/term v0.37.0 - golang.org/x/text v0.30.0 + golang.org/x/text v0.31.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.34.1 k8s.io/apiextensions-apiserver v0.34.1 @@ -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.28.0 // indirect - golang.org/x/net v0.45.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/net v0.46.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.17.0 // indirect + golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.37.0 // indirect + golang.org/x/tools v0.38.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 32dbbbb70..eabf05d97 100644 --- a/go.sum +++ b/go.sum @@ -396,16 +396,16 @@ 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.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= 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= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= -golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +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/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= @@ -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.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= -golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= 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= @@ -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.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +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/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.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 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.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +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/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 40856bf50cdaa6a178b1c8433c98c25004e44735 Mon Sep 17 00:00:00 2001 From: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:27:23 -0500 Subject: [PATCH 044/114] docs: Fix LFX Health Score badge URL in README.md The URL must have changed, because the badge is currently broken in the readme. Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 66fdab041..22422d5a4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/helm.sh/helm/v4) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3131/badge)](https://bestpractices.coreinfrastructure.org/projects/3131) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/helm/helm/badge)](https://scorecard.dev/viewer/?uri=github.com/helm/helm) -[![LFX Health Score](https://insights.production.lfx.dev/api/badge/health-score?project=helm)](https://insights.linuxfoundation.org/project/helm) +[![LFX Health Score](https://insights.linuxfoundation.org/api/badge/health-score?project=helm)](https://insights.linuxfoundation.org/project/helm) Helm is a tool for managing Charts. Charts are packages of pre-configured Kubernetes resources. From c81e2676535296a00f70402deac17a2b9888958d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:54:16 +0000 Subject: [PATCH 045/114] 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.1` | `0.34.2` | | [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) | `0.34.1` | `0.34.2` | | [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) | `0.34.1` | `0.34.2` | | [k8s.io/apiserver](https://github.com/kubernetes/apiserver) | `0.34.1` | `0.34.2` | | [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime) | `0.34.1` | `0.34.2` | | [k8s.io/client-go](https://github.com/kubernetes/client-go) | `0.34.1` | `0.34.2` | | [k8s.io/kubectl](https://github.com/kubernetes/kubectl) | `0.34.1` | `0.34.2` | Updates `k8s.io/api` from 0.34.1 to 0.34.2 - [Commits](https://github.com/kubernetes/api/compare/v0.34.1...v0.34.2) Updates `k8s.io/apiextensions-apiserver` from 0.34.1 to 0.34.2 - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.34.1...v0.34.2) Updates `k8s.io/apimachinery` from 0.34.1 to 0.34.2 - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.34.1...v0.34.2) Updates `k8s.io/apiserver` from 0.34.1 to 0.34.2 - [Commits](https://github.com/kubernetes/apiserver/compare/v0.34.1...v0.34.2) Updates `k8s.io/cli-runtime` from 0.34.1 to 0.34.2 - [Commits](https://github.com/kubernetes/cli-runtime/compare/v0.34.1...v0.34.2) Updates `k8s.io/client-go` from 0.34.1 to 0.34.2 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.34.1...v0.34.2) Updates `k8s.io/kubectl` from 0.34.1 to 0.34.2 - [Commits](https://github.com/kubernetes/kubectl/compare/v0.34.1...v0.34.2) --- updated-dependencies: - dependency-name: k8s.io/api dependency-version: 0.34.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apiextensions-apiserver dependency-version: 0.34.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apimachinery dependency-version: 0.34.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/apiserver dependency-version: 0.34.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/cli-runtime dependency-version: 0.34.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/client-go dependency-version: 0.34.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/kubectl dependency-version: 0.34.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 29520a019..4975fb7c4 100644 --- a/go.mod +++ b/go.mod @@ -40,14 +40,14 @@ require ( golang.org/x/term v0.37.0 golang.org/x/text v0.31.0 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.1 - k8s.io/apiextensions-apiserver v0.34.1 - k8s.io/apimachinery v0.34.1 - k8s.io/apiserver v0.34.1 - k8s.io/cli-runtime v0.34.1 - k8s.io/client-go v0.34.1 + 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/klog/v2 v2.130.1 - k8s.io/kubectl v0.34.1 + k8s.io/kubectl v0.34.2 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.1 // indirect + k8s.io/component-base v0.34.2 // 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 eabf05d97..ba032ef14 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.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= -k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= -k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= -k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= -k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= -k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= -k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= -k8s.io/cli-runtime v0.34.1 h1:btlgAgTrYd4sk8vJTRG6zVtqBKt9ZMDeQZo2PIzbL7M= -k8s.io/cli-runtime v0.34.1/go.mod h1:aVA65c+f0MZiMUPbseU/M9l1Wo2byeaGwUuQEQVVveE= -k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= -k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= -k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= -k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +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/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.1 h1:1qP1oqT5Xc93K+H8J7ecpBjaz511gan89KO9Vbsh/OI= -k8s.io/kubectl v0.34.1/go.mod h1:JRYlhJpGPyk3dEmJ+BuBiOB9/dAvnrALJEiY/C5qa6A= +k8s.io/kubectl v0.34.2 h1:+fWGrVlDONMUmmQLDaGkQ9i91oszjjRAa94cr37hzqA= +k8s.io/kubectl v0.34.2/go.mod h1:X2KTOdtZZNrTWmUD4oHApJ836pevSl+zvC5sI6oO2YQ= 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 faa0adcb3855a299596f68f7a18f3dd84ef9ed1a Mon Sep 17 00:00:00 2001 From: Fish-pro Date: Wed, 18 Jan 2023 10:54:12 +0800 Subject: [PATCH 046/114] Fix syntax errors in the document Signed-off-by: Fish-pro --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ab93403d..e809e7ca2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -120,7 +120,7 @@ specific upcoming bugfix or feature release could fall into one of two different Issues and PRs which are deemed backwards-incompatible may be added to the discussion items for Helm 4 with [label:v4.x](https://github.com/helm/helm/labels/v4.x). An issue or PR that we are not -sure we will be addressing will not be added to any milestone. +sure if we will be addressing will not be added to any milestone. A milestone (and hence release) can be closed when all outstanding issues/PRs have been closed or moved to another milestone and the associated release has been published. @@ -160,8 +160,8 @@ There are 5 types of issues (each with their own corresponding [label](#labels)) 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 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 or - not a feature needs a proposal is up to the core maintainers. All issues that are proposals should + 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. - `feature`: These track specific feature requests and ideas until they are complete. They can @@ -179,7 +179,7 @@ below. 2. Triage - The maintainer in charge of triaging will apply the proper labels for the issue. This includes labels for priority, type, and metadata (such as `good first issue`). The only issue priority - we will be tracking is whether or not the issue is "critical." If additional levels are needed + we will be tracking is whether the issue is "critical." If additional levels are needed in the future, we will add them. - (If needed) Clean up the title to succinctly and clearly state the issue. Also ensure that proposals are prefaced with "Proposal: [the rest of the title]". From c04e18e45253f08d127a37c8328e7084e486c7cc Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Wed, 12 Nov 2025 21:23:01 +0100 Subject: [PATCH 047/114] 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 048/114] 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 049/114] 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 050/114] 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 051/114] 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 b6a8c6521614f99d54752110a955b4f6fb5b8f5a Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Fri, 14 Nov 2025 21:11:45 +0100 Subject: [PATCH 052/114] fix: correct LDFLAGS path for default Kubernetes version The Makefile LDFLAGS were pointing to an incorrect package path (helm.sh/helm/v4/pkg/chart/common/util) that doesn't exist, causing the k8sVersionMajor and k8sVersionMinor variables to silently fail to be set. This left the hardcoded defaults (1.20) in place. After this fix, `helm template` now correctly defaults to Kubernetes v1.34.0 (matching the bundled client-go version) instead of v1.20.0, ensuring charts render with current stable API versions. Testing scenario: ```sh cat > /tmp/test-chart/Chart.yaml << 'EOF' apiVersion: v2 name: test-chart version: 1.0.0 kubeVersion: ">= 1.28.0-0" EOF ./bin/helm template test-release /tmp/test-chart cat > /tmp/test-chart-fail/Chart.yaml << 'EOF' apiVersion: v2 name: test-chart-fail version: 1.0.0 kubeVersion: ">= 1.35.0-0" EOF ./bin/helm template test-release /tmp/test-chart-fail Exit code 1 Error: chart requires kubeVersion: >= 1.35.0-0 which is incompatible with Kubernetes v1.34.0 ``` Fixes #31508 Closes #31501 Closes #31502 Signed-off-by: Benoit Tigeot --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 4cf779438..d7422bf23 100644 --- a/Makefile +++ b/Makefile @@ -67,8 +67,8 @@ LDFLAGS += -X helm.sh/helm/v4/pkg/chart/v2/lint/rules.k8sVersionMajor=$(K8S_MODU 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/util.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER) -LDFLAGS += -X helm.sh/helm/v4/pkg/chart/common/util.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) From 2d49f0cb4ab1996bc203c9a91454021a375e522c Mon Sep 17 00:00:00 2001 From: yxxhero Date: Sat, 15 Nov 2025 11:16:08 +0800 Subject: [PATCH 053/114] 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 054/114] 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 055/114] 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 056/114] 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 057/114] 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 058/114] 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 059/114] 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 060/114] 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 061/114] 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 062/114] 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 063/114] 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 064/114] 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 065/114] 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 066/114] 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 067/114] 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 068/114] 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 069/114] 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 070/114] 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 071/114] 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 072/114] 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 073/114] 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 074/114] 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 075/114] 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 076/114] 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 077/114] 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 078/114] 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 b859163fd2b71d84111b3caaab1c72ab4852d26d Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Sun, 23 Nov 2025 10:49:13 -0800 Subject: [PATCH 079/114] 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 e71a29ce4ff045c8194625c0d804f789d121023e Mon Sep 17 00:00:00 2001 From: megha1906 Date: Wed, 26 Nov 2025 18:00:37 +0530 Subject: [PATCH 080/114] 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 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 081/114] 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 faa8912e36bfebf45735fbd6cbd709181b311d22 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Thu, 27 Nov 2025 08:55:42 +0100 Subject: [PATCH 082/114] 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 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 083/114] 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 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 084/114] 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 085/114] 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 a18e59e46516bfe07241bf2e89de6268660a13ae Mon Sep 17 00:00:00 2001 From: Mads Jensen Date: Mon, 8 Dec 2025 19:52:01 +0100 Subject: [PATCH 086/114] 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 087/114] 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 088/114] 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 089/114] 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 090/114] 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 091/114] 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 092/114] 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 093/114] 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 094/114] 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 095/114] 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 096/114] 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 097/114] 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 098/114] 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 099/114] 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 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 100/114] 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 101/114] 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 102/114] 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 103/114] 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 f92ae18977bfe4fad74c7cb0e8c7f7fc68d85306 Mon Sep 17 00:00:00 2001 From: Deepak Date: Tue, 16 Dec 2025 11:35:11 +0530 Subject: [PATCH 104/114] 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 105/114] 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 106/114] 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 107/114] 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 108/114] 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 109/114] 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 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 110/114] 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 111/114] 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 112/114] 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 1c119bcaa6f68a73f27a21f9d7dba22c4baf4d7d Mon Sep 17 00:00:00 2001 From: Brenden Ehlers Date: Sun, 21 Dec 2025 08:44:32 -0700 Subject: [PATCH 113/114] 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 114/114] 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: