From 268593bf2e9769ef4b75328b33dfb4195e6e9e5a Mon Sep 17 00:00:00 2001 From: Manuel Alonso <434575+manute@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:21:02 +0100 Subject: [PATCH 01/26] fix(action): crd resources can be empty Signed-off-by: Manuel Alonso <434575+manute@users.noreply.github.com> --- pkg/action/install.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/action/install.go b/pkg/action/install.go index 2f5910284..b0c28132a 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -186,6 +186,10 @@ func (i *Install) installCRDs(crds []chart.CRD) error { return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err) } + if res == nil { + return fmt.Errorf("failed to install CRD %s: resources are empty", obj.Name) + } + // Send them to Kube if _, err := i.cfg.KubeClient.Create( res, From 52235cc0bf7d0c8faf17c7dc8cddd77f93434aea Mon Sep 17 00:00:00 2001 From: Manuel Alonso <434575+manute@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:33:20 +0100 Subject: [PATCH 02/26] fix(install): check lenght and file nil, add tests Signed-off-by: Manuel Alonso <434575+manute@users.noreply.github.com> --- pkg/action/install.go | 6 +++++- pkg/action/install_test.go | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index b0c28132a..b2e8f8bf4 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -180,13 +180,17 @@ func (i *Install) installCRDs(crds []chart.CRD) error { // We do these one file at a time in the order they were read. totalItems := []*resource.Info{} for _, obj := range crds { + if obj.File == nil { + return fmt.Errorf("failed to install CRD %s: file is empty", obj.Name) + } + // Read in the resources res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.File.Data), false) if err != nil { return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err) } - if res == nil { + if len(res) == 0 { return fmt.Errorf("failed to install CRD %s: resources are empty", obj.Name) } diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 9f04f40d4..a9a33881a 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -45,6 +45,7 @@ import ( "helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/pkg/chart/common" + chart "helm.sh/helm/v4/pkg/chart/v2" "helm.sh/helm/v4/pkg/kube" kubefake "helm.sh/helm/v4/pkg/kube/fake" rcommon "helm.sh/helm/v4/pkg/release/common" @@ -1068,3 +1069,42 @@ func TestInstallRun_UnreachableKubeClient(t *testing.T) { assert.Nil(t, res) assert.ErrorContains(t, err, "connection refused") } + +func TestInstallCRDs_CheckNilErrors(t *testing.T) { + tests := []struct { + name string + input []chart.CRD + }{ + { + name: "only one crd with file nil", + input: []chart.CRD{ + {Name: "one", File: nil}, + }, + }, + { + name: "only one crd with its file data nil", + input: []chart.CRD{ + {Name: "one", File: &common.File{Name: "crds/foo.yaml", Data: nil}}, + }, + }, + { + name: "at least a crd with its file data nil", + input: []chart.CRD{ + {Name: "one", File: &common.File{Name: "crds/foo.yaml", Data: []byte("data")}}, + {Name: "two", File: &common.File{Name: "crds/foo.yaml", Data: nil}}, + {Name: "three", File: &common.File{Name: "crds/foo.yaml", Data: []byte("data")}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + instAction := installAction(t) + + err := instAction.installCRDs(tt.input) + if err == nil { + t.Errorf("got error expected nil") + } + }) + } +} From 0357e8d0f7eab074252ca49e1ca3aded834a001d Mon Sep 17 00:00:00 2001 From: Manuel Alonso <434575+manute@users.noreply.github.com> Date: Mon, 22 Dec 2025 19:15:16 +0100 Subject: [PATCH 03/26] fix(test): no check empty resources Signed-off-by: Manuel Alonso <434575+manute@users.noreply.github.com> --- pkg/action/install.go | 4 ---- pkg/action/install_test.go | 16 ++++++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index b2e8f8bf4..57839b289 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -190,10 +190,6 @@ func (i *Install) installCRDs(crds []chart.CRD) error { return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err) } - if len(res) == 0 { - return fmt.Errorf("failed to install CRD %s: resources are empty", obj.Name) - } - // Send them to Kube if _, err := i.cfg.KubeClient.Create( res, diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index a9a33881a..38ea556f5 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -1072,28 +1072,32 @@ func TestInstallRun_UnreachableKubeClient(t *testing.T) { func TestInstallCRDs_CheckNilErrors(t *testing.T) { tests := []struct { - name string - input []chart.CRD + name string + input []chart.CRD + expectedErr bool }{ { name: "only one crd with file nil", input: []chart.CRD{ {Name: "one", File: nil}, }, + expectedErr: true, }, { name: "only one crd with its file data nil", input: []chart.CRD{ {Name: "one", File: &common.File{Name: "crds/foo.yaml", Data: nil}}, }, + expectedErr: false, }, { name: "at least a crd with its file data nil", input: []chart.CRD{ {Name: "one", File: &common.File{Name: "crds/foo.yaml", Data: []byte("data")}}, - {Name: "two", File: &common.File{Name: "crds/foo.yaml", Data: nil}}, - {Name: "three", File: &common.File{Name: "crds/foo.yaml", Data: []byte("data")}}, + {Name: "two", File: &common.File{Name: "crds/foo2.yaml", Data: nil}}, + {Name: "three", File: &common.File{Name: "crds/foo3.yaml", Data: []byte("data")}}, }, + expectedErr: false, }, } @@ -1102,8 +1106,8 @@ func TestInstallCRDs_CheckNilErrors(t *testing.T) { instAction := installAction(t) err := instAction.installCRDs(tt.input) - if err == nil { - t.Errorf("got error expected nil") + if tt.expectedErr && err == nil { + t.Errorf("got error %v expected nil", err) } }) } From 00f0a48a7dae379c2b6bd0dea43984d42b27a494 Mon Sep 17 00:00:00 2001 From: Manuel Alonso <434575+manute@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:35:14 +0100 Subject: [PATCH 04/26] fix(install): add more tests and check nil file data Signed-off-by: Manuel Alonso <434575+manute@users.noreply.github.com> --- pkg/action/install.go | 8 ++++++++ pkg/action/install_test.go | 12 ++++-------- pkg/cmd/install_test.go | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index 57839b289..e832bef0a 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -184,12 +184,20 @@ func (i *Install) installCRDs(crds []chart.CRD) error { return fmt.Errorf("failed to install CRD %s: file is empty", obj.Name) } + if obj.File.Data == nil { + return fmt.Errorf("failed to install CRD %s: file data is empty", obj.Name) + } + // Read in the resources res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.File.Data), false) if err != nil { return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err) } + if len(res) == 0 { + return fmt.Errorf("failed to install CRD %s: resources are empty", obj.Name) + } + // Send them to Kube if _, err := i.cfg.KubeClient.Create( res, diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 38ea556f5..11b175df8 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -1072,23 +1072,20 @@ func TestInstallRun_UnreachableKubeClient(t *testing.T) { func TestInstallCRDs_CheckNilErrors(t *testing.T) { tests := []struct { - name string - input []chart.CRD - expectedErr bool + name string + input []chart.CRD }{ { name: "only one crd with file nil", input: []chart.CRD{ {Name: "one", File: nil}, }, - expectedErr: true, }, { name: "only one crd with its file data nil", input: []chart.CRD{ {Name: "one", File: &common.File{Name: "crds/foo.yaml", Data: nil}}, }, - expectedErr: false, }, { name: "at least a crd with its file data nil", @@ -1097,7 +1094,6 @@ func TestInstallCRDs_CheckNilErrors(t *testing.T) { {Name: "two", File: &common.File{Name: "crds/foo2.yaml", Data: nil}}, {Name: "three", File: &common.File{Name: "crds/foo3.yaml", Data: []byte("data")}}, }, - expectedErr: false, }, } @@ -1106,8 +1102,8 @@ func TestInstallCRDs_CheckNilErrors(t *testing.T) { instAction := installAction(t) err := instAction.installCRDs(tt.input) - if tt.expectedErr && err == nil { - t.Errorf("got error %v expected nil", err) + if err == nil { + t.Errorf("got nil expected err") } }) } diff --git a/pkg/cmd/install_test.go b/pkg/cmd/install_test.go index f0f12e4f7..5fa3c1340 100644 --- a/pkg/cmd/install_test.go +++ b/pkg/cmd/install_test.go @@ -240,7 +240,7 @@ func TestInstall(t *testing.T) { // Install chart with only crds { name: "install chart with only crds", - cmd: "install crd-test testdata/testcharts/chart-with-only-crds --namespace default", + cmd: "install crd-test testdata/testcharts/chart-with-only-crds --namespace default --dry-run", }, // Verify the user/pass works { From 561410ae1d09c2aa289ff8d8cad5b7fa979cd135 Mon Sep 17 00:00:00 2001 From: Manuel Alonso Gonzalez Date: Sat, 10 Jan 2026 09:49:30 +0100 Subject: [PATCH 05/26] fix(test): merge fix correctly Signed-off-by: Manuel Alonso Gonzalez <434575+manute@users.noreply.github.com> Signed-off-by: Manuel Alonso Gonzalez --- pkg/action/install_test.go | 113 +++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 8aa243663..fc8a96319 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -1074,6 +1074,119 @@ func TestInstallRun_UnreachableKubeClient(t *testing.T) { assert.ErrorContains(t, err, "connection refused") } +func TestInstallSetRegistryClient(t *testing.T) { + config := actionConfigFixture(t) + instAction := NewInstall(config) + + registryClient := ®istry.Client{} + instAction.SetRegistryClient(registryClient) + + assert.Equal(t, registryClient, instAction.GetRegistryClient()) +} + +func TestInstalLCRDs(t *testing.T) { + config := actionConfigFixture(t) + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + assert.Len(t, crdsToInstall, 1) + assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) + + require.NoError(t, instAction.installCRDs(crdsToInstall)) +} + +func TestInstalLCRDs_KubeClient_BuildError(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.BuildError = errors.New("build error") + config.KubeClient = &failingKubeClient + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + + require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") +} + +func TestInstalLCRDs_KubeClient_CreateError(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.CreateError = errors.New("create error") + config.KubeClient = &failingKubeClient + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + + require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") +} + +func TestInstalLCRDs_AlreadyExist(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + mockError := &apierrors.StatusError{ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Reason: metav1.StatusReasonAlreadyExists, + }} + failingKubeClient.CreateError = mockError + config.KubeClient = &failingKubeClient + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + + assert.Nil(t, instAction.installCRDs(crdsToInstall)) +} + +func TestInstalLCRDs_WaiterError(t *testing.T) { + config := actionConfigFixture(t) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} + failingKubeClient.WaitError = errors.New("wait error") + failingKubeClient.BuildDummy = true + config.KubeClient = &failingKubeClient + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + + require.Error(t, instAction.installCRDs(crdsToInstall), "wait error") +} + +func TestCheckDependencies(t *testing.T) { + dependency := chart.Dependency{Name: "hello"} + mockChart := buildChart(withDependency()) + + assert.Nil(t, CheckDependencies(mockChart, []ci.Dependency{&dependency})) +} + +func TestCheckDependencies_MissingDependency(t *testing.T) { + dependency := chart.Dependency{Name: "missing"} + mockChart := buildChart(withDependency()) + + assert.ErrorContains(t, CheckDependencies(mockChart, []ci.Dependency{&dependency}), "missing in charts") +} + func TestInstallCRDs_CheckNilErrors(t *testing.T) { tests := []struct { name string From 0f949a92c149cf11e5bb19caf4d19d05567be6eb Mon Sep 17 00:00:00 2001 From: Manuel Alonso Date: Sun, 11 Jan 2026 21:08:31 +0100 Subject: [PATCH 06/26] fix(test): fix tests and check nil for restclient Signed-off-by: Manuel Alonso --- pkg/action/install.go | 48 ++++++++++++++++++++------------------ pkg/action/install_test.go | 15 ++++++------ 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index f1677c297..b84e9eaf9 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -222,32 +222,34 @@ func (i *Install) installCRDs(crds []chart.CRD) error { return err } - // If we have already gathered the capabilities, we need to invalidate - // the cache so that the new CRDs are recognized. This should only be - // the case when an action configuration is reused for multiple actions, - // as otherwise it is later loaded by ourselves when getCapabilities - // is called later on in the installation process. - if i.cfg.Capabilities != nil { - discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() + if i.cfg.RESTClientGetter != nil { + // If we have already gathered the capabilities, we need to invalidate + // the cache so that the new CRDs are recognized. This should only be + // the case when an action configuration is reused for multiple actions, + // as otherwise it is later loaded by ourselves when getCapabilities + // is called later on in the installation process. + if i.cfg.Capabilities != nil { + discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() + if err != nil { + return err + } + + i.cfg.Logger().Debug("clearing discovery cache") + discoveryClient.Invalidate() + + _, _ = discoveryClient.ServerGroups() + } + + // Invalidate the REST mapper, since it will not have the new CRDs + // present. + restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper() if err != nil { return err } - - i.cfg.Logger().Debug("clearing discovery cache") - discoveryClient.Invalidate() - - _, _ = discoveryClient.ServerGroups() - } - - // Invalidate the REST mapper, since it will not have the new CRDs - // present. - restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper() - if err != nil { - return err - } - if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { - i.cfg.Logger().Debug("clearing REST mapper cache") - resettable.Reset() + if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { + i.cfg.Logger().Debug("clearing REST mapper cache") + resettable.Reset() + } } } return nil diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index fc8a96319..76c35b628 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -1084,8 +1084,8 @@ func TestInstallSetRegistryClient(t *testing.T) { assert.Equal(t, registryClient, instAction.GetRegistryClient()) } -func TestInstalLCRDs(t *testing.T) { - config := actionConfigFixture(t) +func TestInstallCRDs(t *testing.T) { + config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false)) instAction := NewInstall(config) mockFile := common.File{ @@ -1100,7 +1100,7 @@ func TestInstalLCRDs(t *testing.T) { require.NoError(t, instAction.installCRDs(crdsToInstall)) } -func TestInstalLCRDs_KubeClient_BuildError(t *testing.T) { +func TestInstallCRDs_KubeClient_BuildError(t *testing.T) { config := actionConfigFixture(t) failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} failingKubeClient.BuildError = errors.New("build error") @@ -1117,7 +1117,7 @@ func TestInstalLCRDs_KubeClient_BuildError(t *testing.T) { require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") } -func TestInstalLCRDs_KubeClient_CreateError(t *testing.T) { +func TestInstallCRDs_KubeClient_CreateError(t *testing.T) { config := actionConfigFixture(t) failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} failingKubeClient.CreateError = errors.New("create error") @@ -1134,7 +1134,7 @@ func TestInstalLCRDs_KubeClient_CreateError(t *testing.T) { require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") } -func TestInstalLCRDs_AlreadyExist(t *testing.T) { +func TestInstallCRDs_AlreadyExist(t *testing.T) { config := actionConfigFixture(t) failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} mockError := &apierrors.StatusError{ErrStatus: metav1.Status{ @@ -1149,13 +1149,14 @@ func TestInstalLCRDs_AlreadyExist(t *testing.T) { Name: "crds/foo.yaml", Data: []byte("hello"), } + mockChart := buildChart(withFile(mockFile)) crdsToInstall := mockChart.CRDObjects() - assert.Nil(t, instAction.installCRDs(crdsToInstall)) + require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") } -func TestInstalLCRDs_WaiterError(t *testing.T) { +func TestInstallCRDs_WaiterError(t *testing.T) { config := actionConfigFixture(t) failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} failingKubeClient.WaitError = errors.New("wait error") From 6501ef490a45e9b7edfed1432702532c5b11c6d2 Mon Sep 17 00:00:00 2001 From: Manuel Alonso Date: Tue, 13 Jan 2026 19:21:16 +0100 Subject: [PATCH 07/26] chore(refactor): better testing and functionality for installing crd Signed-off-by: Manuel Alonso --- pkg/action/install.go | 48 ++++++++++++++++++-------------------- pkg/action/install_test.go | 40 +++++++++++-------------------- 2 files changed, 37 insertions(+), 51 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index b84e9eaf9..f1677c297 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -222,34 +222,32 @@ func (i *Install) installCRDs(crds []chart.CRD) error { return err } - if i.cfg.RESTClientGetter != nil { - // If we have already gathered the capabilities, we need to invalidate - // the cache so that the new CRDs are recognized. This should only be - // the case when an action configuration is reused for multiple actions, - // as otherwise it is later loaded by ourselves when getCapabilities - // is called later on in the installation process. - if i.cfg.Capabilities != nil { - discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() - if err != nil { - return err - } - - i.cfg.Logger().Debug("clearing discovery cache") - discoveryClient.Invalidate() - - _, _ = discoveryClient.ServerGroups() - } - - // Invalidate the REST mapper, since it will not have the new CRDs - // present. - restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper() + // If we have already gathered the capabilities, we need to invalidate + // the cache so that the new CRDs are recognized. This should only be + // the case when an action configuration is reused for multiple actions, + // as otherwise it is later loaded by ourselves when getCapabilities + // is called later on in the installation process. + if i.cfg.Capabilities != nil { + discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() if err != nil { return err } - if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { - i.cfg.Logger().Debug("clearing REST mapper cache") - resettable.Reset() - } + + i.cfg.Logger().Debug("clearing discovery cache") + discoveryClient.Invalidate() + + _, _ = discoveryClient.ServerGroups() + } + + // Invalidate the REST mapper, since it will not have the new CRDs + // present. + restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper() + if err != nil { + return err + } + if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { + i.cfg.Logger().Debug("clearing REST mapper cache") + resettable.Reset() } } return nil diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 76c35b628..003a29528 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -35,7 +35,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kuberuntime "k8s.io/apimachinery/pkg/runtime" @@ -45,6 +44,7 @@ import ( "k8s.io/client-go/rest/fake" ci "helm.sh/helm/v4/pkg/chart" + "helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/pkg/chart/common" @@ -1086,6 +1086,8 @@ func TestInstallSetRegistryClient(t *testing.T) { func TestInstallCRDs(t *testing.T) { config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false)) + config.RESTClientGetter = cli.New().RESTClientGetter() + instAction := NewInstall(config) mockFile := common.File{ @@ -1094,10 +1096,18 @@ func TestInstallCRDs(t *testing.T) { } mockChart := buildChart(withFile(mockFile)) crdsToInstall := mockChart.CRDObjects() - assert.Len(t, crdsToInstall, 1) - assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) - require.NoError(t, instAction.installCRDs(crdsToInstall)) + t.Run("fresh installation", func(t *testing.T) { + assert.Len(t, crdsToInstall, 1) + assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) + require.NoError(t, instAction.installCRDs(crdsToInstall)) + }) + + t.Run("already exist", func(t *testing.T) { + assert.Len(t, crdsToInstall, 1) + assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) + require.NoError(t, instAction.installCRDs(crdsToInstall)) + }) } func TestInstallCRDs_KubeClient_BuildError(t *testing.T) { @@ -1134,28 +1144,6 @@ func TestInstallCRDs_KubeClient_CreateError(t *testing.T) { require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") } -func TestInstallCRDs_AlreadyExist(t *testing.T) { - config := actionConfigFixture(t) - failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} - mockError := &apierrors.StatusError{ErrStatus: metav1.Status{ - Status: metav1.StatusFailure, - Reason: metav1.StatusReasonAlreadyExists, - }} - failingKubeClient.CreateError = mockError - config.KubeClient = &failingKubeClient - instAction := NewInstall(config) - - mockFile := common.File{ - Name: "crds/foo.yaml", - Data: []byte("hello"), - } - - mockChart := buildChart(withFile(mockFile)) - crdsToInstall := mockChart.CRDObjects() - - require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD") -} - func TestInstallCRDs_WaiterError(t *testing.T) { config := actionConfigFixture(t) failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil} From 111d4e6e0e86af6ba25a355be1a7599f5258ee58 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 6 Feb 2026 16:10:41 +0100 Subject: [PATCH 08/26] chore(pkg): fix modernize linter #### Description fix modernize linter in pkg/chart/common/util Signed-off-by: Matthieu MOREL --- pkg/action/upgrade.go | 12 ++++++------ pkg/chart/common.go | 18 +++++++++--------- pkg/chart/common/util/jsonschema_test.go | 18 +++++++++--------- pkg/chart/common/util/values_test.go | 14 +++++++------- pkg/chart/v2/lint/rules/values_test.go | 18 +++++++++--------- pkg/chart/v2/loader/load.go | 14 +++++++------- pkg/engine/lookup_func.go | 14 +++++++------- 7 files changed, 54 insertions(+), 54 deletions(-) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 4c93855b1..4c5a5b55e 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -156,13 +156,13 @@ func (u *Upgrade) SetRegistryClient(client *registry.Client) { } // Run executes the upgrade on the given release. -func (u *Upgrade) Run(name string, chart chart.Charter, vals map[string]interface{}) (ri.Releaser, error) { +func (u *Upgrade) Run(name string, chart chart.Charter, vals map[string]any) (ri.Releaser, error) { ctx := context.Background() return u.RunWithContext(ctx, name, chart, vals) } // RunWithContext executes the upgrade on the given release with context. -func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Charter, vals map[string]interface{}) (ri.Releaser, error) { +func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Charter, vals map[string]any) (ri.Releaser, error) { if err := u.cfg.KubeClient.IsReachable(); err != nil { return nil, err } @@ -213,7 +213,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Char } // prepareUpgrade builds an upgraded release for an upgrade operation. -func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[string]interface{}) (*release.Release, *release.Release, bool, error) { +func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[string]any) (*release.Release, *release.Release, bool, error) { if chart == nil { return nil, nil, false, errMissingChart } @@ -408,7 +408,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR } rChan := make(chan resultMessage) ctxChan := make(chan resultMessage) - doneChan := make(chan interface{}) + doneChan := make(chan any) defer close(doneChan) go u.releasingUpgrade(rChan, upgradedRelease, current, target, originalRelease, serverSideApply) go u.handleContext(ctx, doneChan, ctxChan, upgradedRelease) @@ -434,7 +434,7 @@ func (u *Upgrade) reportToPerformUpgrade(c chan<- resultMessage, rel *release.Re } // Setup listener for SIGINT and SIGTERM -func (u *Upgrade) handleContext(ctx context.Context, done chan interface{}, c chan<- resultMessage, upgradedRelease *release.Release) { +func (u *Upgrade) handleContext(ctx context.Context, done chan any, c chan<- resultMessage, upgradedRelease *release.Release) { select { case <-ctx.Done(): err := ctx.Err() @@ -601,7 +601,7 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e // // This is skipped if the u.ResetValues flag is set, in which case the // request values are not altered. -func (u *Upgrade) reuseValues(chart *chartv2.Chart, current *release.Release, newVals map[string]interface{}) (map[string]interface{}, error) { +func (u *Upgrade) reuseValues(chart *chartv2.Chart, current *release.Release, newVals map[string]any) (map[string]any, error) { if u.ResetValues { // If ResetValues is set, we completely ignore current.Config. u.cfg.Logger().Debug("resetting values to the chart's original version") diff --git a/pkg/chart/common.go b/pkg/chart/common.go index cd87e91e7..288227953 100644 --- a/pkg/chart/common.go +++ b/pkg/chart/common.go @@ -56,8 +56,8 @@ func (r *v2Accessor) IsRoot() bool { return r.chrt.IsRoot() } -func (r *v2Accessor) MetadataAsMap() map[string]interface{} { - var ret map[string]interface{} +func (r *v2Accessor) MetadataAsMap() map[string]any { + var ret map[string]any if r.chrt.Metadata == nil { return ret } @@ -101,7 +101,7 @@ func (r *v2Accessor) MetaDependencies() []Dependency { return deps } -func (r *v2Accessor) Values() map[string]interface{} { +func (r *v2Accessor) Values() map[string]any { return r.chrt.Values } @@ -125,8 +125,8 @@ func (r *v3Accessor) IsRoot() bool { return r.chrt.IsRoot() } -func (r *v3Accessor) MetadataAsMap() map[string]interface{} { - var ret map[string]interface{} +func (r *v3Accessor) MetadataAsMap() map[string]any { + var ret map[string]any if r.chrt.Metadata == nil { return ret } @@ -170,7 +170,7 @@ func (r *v3Accessor) MetaDependencies() []Dependency { return deps } -func (r *v3Accessor) Values() map[string]interface{} { +func (r *v3Accessor) Values() map[string]any { return r.chrt.Values } @@ -182,7 +182,7 @@ func (r *v3Accessor) Deprecated() bool { return r.chrt.Metadata.Deprecated } -func structToMap(obj interface{}) (map[string]interface{}, error) { +func structToMap(obj any) (map[string]any, error) { objValue := reflect.ValueOf(obj) // If the value is a pointer, dereference it @@ -195,7 +195,7 @@ func structToMap(obj interface{}) (map[string]interface{}, error) { return nil, fmt.Errorf("input must be a struct or a pointer to a struct") } - result := make(map[string]interface{}) + result := make(map[string]any) objType := objValue.Type() for i := 0; i < objValue.NumField(); i++ { @@ -221,7 +221,7 @@ func structToMap(obj interface{}) (map[string]interface{}, error) { result[field.Name] = nestedMap } case reflect.Slice: - sliceOfMaps := make([]interface{}, value.Len()) + sliceOfMaps := make([]any, value.Len()) for j := 0; j < value.Len(); j++ { sliceElement := value.Index(j) if sliceElement.Kind() == reflect.Struct || sliceElement.Kind() == reflect.Pointer { diff --git a/pkg/chart/common/util/jsonschema_test.go b/pkg/chart/common/util/jsonschema_test.go index 834b1faf6..24073175c 100644 --- a/pkg/chart/common/util/jsonschema_test.go +++ b/pkg/chart/common/util/jsonschema_test.go @@ -138,9 +138,9 @@ func TestValidateAgainstSchema(t *testing.T) { } chrt.AddDependency(subchart) - vals := map[string]interface{}{ + vals := map[string]any{ "name": "John", - "subchart": map[string]interface{}{ + "subchart": map[string]any{ "age": 25, }, } @@ -165,9 +165,9 @@ func TestValidateAgainstSchemaNegative(t *testing.T) { } chrt.AddDependency(subchart) - vals := map[string]interface{}{ + vals := map[string]any{ "name": "John", - "subchart": map[string]interface{}{}, + "subchart": map[string]any{}, } var errString string @@ -200,9 +200,9 @@ func TestValidateAgainstSchema2020(t *testing.T) { } chrt.AddDependency(subchart) - vals := map[string]interface{}{ + vals := map[string]any{ "name": "John", - "subchart": map[string]interface{}{ + "subchart": map[string]any{ "data": []any{"hello", 12}, }, } @@ -227,9 +227,9 @@ func TestValidateAgainstSchema2020Negative(t *testing.T) { } chrt.AddDependency(subchart) - vals := map[string]interface{}{ + vals := map[string]any{ "name": "John", - "subchart": map[string]interface{}{ + "subchart": map[string]any{ "data": []any{12}, }, } @@ -294,7 +294,7 @@ func TestValidateAgainstSingleSchema_UnresolvedURN_Ignored(t *testing.T) { "$schema": "https://json-schema.org/draft-07/schema#", "$ref": "urn:example:helm:schemas:v1:helm-schema-validation-conditions:v1/helmSchemaValidation-true" }`) - vals := map[string]interface{}{"any": "value"} + vals := map[string]any{"any": "value"} if err := ValidateAgainstSingleSchema(vals, schema); err != nil { t.Fatalf("expected no error when URN unresolved is ignored, got: %v", err) } diff --git a/pkg/chart/common/util/values_test.go b/pkg/chart/common/util/values_test.go index 706d3cfda..1dea04132 100644 --- a/pkg/chart/common/util/values_test.go +++ b/pkg/chart/common/util/values_test.go @@ -26,17 +26,17 @@ import ( func TestToRenderValues(t *testing.T) { - chartValues := map[string]interface{}{ + chartValues := map[string]any{ "name": "al Rashid", - "where": map[string]interface{}{ + "where": map[string]any{ "city": "Basrah", "title": "caliph", }, } - overrideValues := map[string]interface{}{ + overrideValues := map[string]any{ "name": "Haroun", - "where": map[string]interface{}{ + "where": map[string]any{ "city": "Baghdad", "date": "809 CE", }, @@ -67,11 +67,11 @@ func TestToRenderValues(t *testing.T) { } // Ensure that the top-level values are all set. - metamap := res["Chart"].(map[string]interface{}) + metamap := res["Chart"].(map[string]any) if name := metamap["Name"]; name.(string) != "test" { t.Errorf("Expected chart name 'test', got %q", name) } - relmap := res["Release"].(map[string]interface{}) + relmap := res["Release"].(map[string]any) if name := relmap["Name"]; name.(string) != "Seven Voyages" { t.Errorf("Expected release name 'Seven Voyages', got %q", name) } @@ -98,7 +98,7 @@ func TestToRenderValues(t *testing.T) { if vals["name"] != "Haroun" { t.Errorf("Expected 'Haroun', got %q (%v)", vals["name"], vals) } - where := vals["where"].(map[string]interface{}) + where := vals["where"].(map[string]any) expects := map[string]string{ "city": "Baghdad", "date": "809 CE", diff --git a/pkg/chart/v2/lint/rules/values_test.go b/pkg/chart/v2/lint/rules/values_test.go index 288b77436..afc544ebd 100644 --- a/pkg/chart/v2/lint/rules/values_test.go +++ b/pkg/chart/v2/lint/rules/values_test.go @@ -67,7 +67,7 @@ func TestValidateValuesFileWellFormed(t *testing.T) { ` tmpdir := ensure.TempFile(t, "values.yaml", []byte(badYaml)) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}, false); err == nil { + if err := validateValuesFile(valfile, map[string]any{}, false); err == nil { t.Fatal("expected values file to fail parsing") } } @@ -78,7 +78,7 @@ func TestValidateValuesFileSchema(t *testing.T) { createTestingSchema(t, tmpdir) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}, false); err != nil { + if err := validateValuesFile(valfile, map[string]any{}, false); err != nil { t.Fatalf("Failed validation with %s", err) } } @@ -91,7 +91,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) { valfile := filepath.Join(tmpdir, "values.yaml") - err := validateValuesFile(valfile, map[string]interface{}{}, false) + err := validateValuesFile(valfile, map[string]any{}, false) if err == nil { t.Fatal("expected values file to fail parsing") } @@ -107,7 +107,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T valfile := filepath.Join(tmpdir, "values.yaml") - err := validateValuesFile(valfile, map[string]interface{}{}, true) + err := validateValuesFile(valfile, map[string]any{}, true) if err != nil { t.Fatal("expected values file to pass parsing because of skipSchemaValidation") } @@ -115,7 +115,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T func TestValidateValuesFileSchemaOverrides(t *testing.T) { yaml := "username: admin" - overrides := map[string]interface{}{ + overrides := map[string]any{ "password": "swordfish", } tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml)) @@ -131,24 +131,24 @@ func TestValidateValuesFile(t *testing.T) { tests := []struct { name string yaml string - overrides map[string]interface{} + overrides map[string]any errorMessage string }{ { name: "value added", yaml: "username: admin", - overrides: map[string]interface{}{"password": "swordfish"}, + overrides: map[string]any{"password": "swordfish"}, }, { name: "value not overridden", yaml: "username: admin\npassword:", - overrides: map[string]interface{}{"username": "anotherUser"}, + overrides: map[string]any{"username": "anotherUser"}, errorMessage: "- at '/password': got null, want string", }, { name: "value overridden", yaml: "username: admin\npassword:", - overrides: map[string]interface{}{"username": "anotherUser", "password": "swordfish"}, + overrides: map[string]any{"username": "anotherUser", "password": "swordfish"}, }, } diff --git a/pkg/chart/v2/loader/load.go b/pkg/chart/v2/loader/load.go index d466e247c..e3c5ce325 100644 --- a/pkg/chart/v2/loader/load.go +++ b/pkg/chart/v2/loader/load.go @@ -209,11 +209,11 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { // // The reader is expected to contain one or more YAML documents, the values of which are merged. // And the values can be either a chart's default values or user-supplied values. -func LoadValues(data io.Reader) (map[string]interface{}, error) { - values := map[string]interface{}{} +func LoadValues(data io.Reader) (map[string]any, error) { + values := map[string]any{} reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) for { - currentMap := map[string]interface{}{} + currentMap := map[string]any{} raw, err := reader.Read() if err != nil { if errors.Is(err, io.EOF) { @@ -231,13 +231,13 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) { // MergeMaps merges two maps. If a key exists in both maps, the value from b will be used. // If the value is a map, the maps will be merged recursively. -func MergeMaps(a, b map[string]interface{}) map[string]interface{} { - out := make(map[string]interface{}, len(a)) +func MergeMaps(a, b map[string]any) map[string]any { + out := make(map[string]any, len(a)) maps.Copy(out, a) for k, v := range b { - if v, ok := v.(map[string]interface{}); ok { + if v, ok := v.(map[string]any); ok { if bv, ok := out[k]; ok { - if bv, ok := bv.(map[string]interface{}); ok { + if bv, ok := bv.(map[string]any); ok { out[k] = MergeMaps(bv, v) continue } diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go index c6ad8d252..52b6ffdaf 100644 --- a/pkg/engine/lookup_func.go +++ b/pkg/engine/lookup_func.go @@ -30,7 +30,7 @@ import ( "k8s.io/client-go/rest" ) -type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) +type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]any, error) // NewLookupFunction returns a function for looking up objects in the cluster. // @@ -55,11 +55,11 @@ func (c clientProviderFromConfig) GetClientFor(apiVersion, kind string) (dynamic } func newLookupFunction(clientProvider ClientProvider) lookupFunc { - return func(apiversion string, kind string, namespace string, name string) (map[string]interface{}, error) { + return func(apiversion string, kind string, namespace string, name string) (map[string]any, error) { var client dynamic.ResourceInterface c, namespaced, err := clientProvider.GetClientFor(apiversion, kind) if err != nil { - return map[string]interface{}{}, err + return map[string]any{}, err } if namespaced && namespace != "" { client = c.Namespace(namespace) @@ -73,9 +73,9 @@ func newLookupFunction(clientProvider ClientProvider) lookupFunc { if apierrors.IsNotFound(err) { // Just return an empty interface when the object was not found. // That way, users can use `if not (lookup ...)` in their templates. - return map[string]interface{}{}, nil + return map[string]any{}, nil } - return map[string]interface{}{}, err + return map[string]any{}, err } return obj.UnstructuredContent(), nil } @@ -85,9 +85,9 @@ func newLookupFunction(clientProvider ClientProvider) lookupFunc { if apierrors.IsNotFound(err) { // Just return an empty interface when the object was not found. // That way, users can use `if not (lookup ...)` in their templates. - return map[string]interface{}{}, nil + return map[string]any{}, nil } - return map[string]interface{}{}, err + return map[string]any{}, err } return obj.UnstructuredContent(), nil } From e2d184c79e9049c19bcc466bfe1289ccc6b73717 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 6 Feb 2026 16:10:51 +0100 Subject: [PATCH 09/26] chore(internal): fix modernize linter #### Description fix modernize linter in internal/chart/v3/lint/rules Signed-off-by: Matthieu MOREL --- internal/chart/v3/lint/rules/chartfile.go | 10 ++++---- internal/chart/v3/lint/rules/template.go | 6 ++--- internal/chart/v3/lint/rules/values.go | 6 ++--- internal/chart/v3/lint/rules/values_test.go | 18 ++++++------- internal/chart/v3/loader/load.go | 14 +++++------ internal/chart/v3/loader/load_test.go | 24 +++++++++--------- internal/chart/v3/util/dependencies.go | 28 ++++++++++----------- 7 files changed, 53 insertions(+), 53 deletions(-) diff --git a/internal/chart/v3/lint/rules/chartfile.go b/internal/chart/v3/lint/rules/chartfile.go index fc246ba80..f171b56b9 100644 --- a/internal/chart/v3/lint/rules/chartfile.go +++ b/internal/chart/v3/lint/rules/chartfile.go @@ -69,15 +69,15 @@ func Chartfile(linter *support.Linter) { linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartDependencies(chartFile)) } -func validateChartVersionType(data map[string]interface{}) error { +func validateChartVersionType(data map[string]any) error { return isStringValue(data, "version") } -func validateChartAppVersionType(data map[string]interface{}) error { +func validateChartAppVersionType(data map[string]any) error { return isStringValue(data, "appVersion") } -func isStringValue(data map[string]interface{}, key string) error { +func isStringValue(data map[string]any, key string) error { value, ok := data[key] if !ok { return nil @@ -214,12 +214,12 @@ func validateChartType(cf *chart.Metadata) error { // loadChartFileForTypeCheck loads the Chart.yaml // in a generic form of a map[string]interface{}, so that the type // of the values can be checked -func loadChartFileForTypeCheck(filename string) (map[string]interface{}, error) { +func loadChartFileForTypeCheck(filename string) (map[string]any, error) { b, err := os.ReadFile(filename) if err != nil { return nil, err } - y := make(map[string]interface{}) + y := make(map[string]any) err = yaml.Unmarshal(b, &y) return y, err } diff --git a/internal/chart/v3/lint/rules/template.go b/internal/chart/v3/lint/rules/template.go index 38e602b7e..35e4940ab 100644 --- a/internal/chart/v3/lint/rules/template.go +++ b/internal/chart/v3/lint/rules/template.go @@ -42,17 +42,17 @@ import ( ) // Templates lints the templates in the Linter. -func Templates(linter *support.Linter, values map[string]interface{}, namespace string, _ bool) { +func Templates(linter *support.Linter, values map[string]any, namespace string, _ bool) { TemplatesWithKubeVersion(linter, values, namespace, nil) } // TemplatesWithKubeVersion lints the templates in the Linter, allowing to specify the kubernetes version. -func TemplatesWithKubeVersion(linter *support.Linter, values map[string]interface{}, namespace string, kubeVersion *common.KubeVersion) { +func TemplatesWithKubeVersion(linter *support.Linter, values map[string]any, namespace string, kubeVersion *common.KubeVersion) { TemplatesWithSkipSchemaValidation(linter, values, namespace, kubeVersion, false) } // TemplatesWithSkipSchemaValidation lints the templates in the Linter, allowing to specify the kubernetes version and if schema validation is enabled or not. -func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string]interface{}, namespace string, kubeVersion *common.KubeVersion, skipSchemaValidation bool) { +func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string]any, namespace string, kubeVersion *common.KubeVersion, skipSchemaValidation bool) { fpath := "templates/" templatesPath := filepath.Join(linter.ChartDir, fpath) diff --git a/internal/chart/v3/lint/rules/values.go b/internal/chart/v3/lint/rules/values.go index 0af9765dd..ba371cbe2 100644 --- a/internal/chart/v3/lint/rules/values.go +++ b/internal/chart/v3/lint/rules/values.go @@ -32,7 +32,7 @@ import ( // they are only tested for well-formedness. // // If additional values are supplied, they are coalesced into the values in values.yaml. -func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]interface{}, skipSchemaValidation bool) { +func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]any, skipSchemaValidation bool) { file := "values.yaml" vf := filepath.Join(linter.ChartDir, file) fileExists := linter.RunLinterRule(support.InfoSev, file, validateValuesFileExistence(vf)) @@ -52,7 +52,7 @@ func validateValuesFileExistence(valuesPath string) error { return nil } -func validateValuesFile(valuesPath string, overrides map[string]interface{}, skipSchemaValidation bool) error { +func validateValuesFile(valuesPath string, overrides map[string]any, skipSchemaValidation bool) error { values, err := common.ReadValuesFile(valuesPath) if err != nil { return fmt.Errorf("unable to parse YAML: %w", err) @@ -63,7 +63,7 @@ func validateValuesFile(valuesPath string, overrides map[string]interface{}, ski // We could change that. For now, though, we retain that strategy, and thus can // coalesce tables (like reuse-values does) instead of doing the full chart // CoalesceValues - coalescedValues := util.CoalesceTables(make(map[string]interface{}, len(overrides)), overrides) + coalescedValues := util.CoalesceTables(make(map[string]any, len(overrides)), overrides) coalescedValues = util.CoalesceTables(coalescedValues, values) ext := filepath.Ext(valuesPath) diff --git a/internal/chart/v3/lint/rules/values_test.go b/internal/chart/v3/lint/rules/values_test.go index 288b77436..afc544ebd 100644 --- a/internal/chart/v3/lint/rules/values_test.go +++ b/internal/chart/v3/lint/rules/values_test.go @@ -67,7 +67,7 @@ func TestValidateValuesFileWellFormed(t *testing.T) { ` tmpdir := ensure.TempFile(t, "values.yaml", []byte(badYaml)) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}, false); err == nil { + if err := validateValuesFile(valfile, map[string]any{}, false); err == nil { t.Fatal("expected values file to fail parsing") } } @@ -78,7 +78,7 @@ func TestValidateValuesFileSchema(t *testing.T) { createTestingSchema(t, tmpdir) valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}, false); err != nil { + if err := validateValuesFile(valfile, map[string]any{}, false); err != nil { t.Fatalf("Failed validation with %s", err) } } @@ -91,7 +91,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) { valfile := filepath.Join(tmpdir, "values.yaml") - err := validateValuesFile(valfile, map[string]interface{}{}, false) + err := validateValuesFile(valfile, map[string]any{}, false) if err == nil { t.Fatal("expected values file to fail parsing") } @@ -107,7 +107,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T valfile := filepath.Join(tmpdir, "values.yaml") - err := validateValuesFile(valfile, map[string]interface{}{}, true) + err := validateValuesFile(valfile, map[string]any{}, true) if err != nil { t.Fatal("expected values file to pass parsing because of skipSchemaValidation") } @@ -115,7 +115,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T func TestValidateValuesFileSchemaOverrides(t *testing.T) { yaml := "username: admin" - overrides := map[string]interface{}{ + overrides := map[string]any{ "password": "swordfish", } tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml)) @@ -131,24 +131,24 @@ func TestValidateValuesFile(t *testing.T) { tests := []struct { name string yaml string - overrides map[string]interface{} + overrides map[string]any errorMessage string }{ { name: "value added", yaml: "username: admin", - overrides: map[string]interface{}{"password": "swordfish"}, + overrides: map[string]any{"password": "swordfish"}, }, { name: "value not overridden", yaml: "username: admin\npassword:", - overrides: map[string]interface{}{"username": "anotherUser"}, + overrides: map[string]any{"username": "anotherUser"}, errorMessage: "- at '/password': got null, want string", }, { name: "value overridden", yaml: "username: admin\npassword:", - overrides: map[string]interface{}{"username": "anotherUser", "password": "swordfish"}, + overrides: map[string]any{"username": "anotherUser", "password": "swordfish"}, }, } diff --git a/internal/chart/v3/loader/load.go b/internal/chart/v3/loader/load.go index 373c4659f..48f346ccf 100644 --- a/internal/chart/v3/loader/load.go +++ b/internal/chart/v3/loader/load.go @@ -182,11 +182,11 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) { // // The reader is expected to contain one or more YAML documents, the values of which are merged. // And the values can be either a chart's default values or user-supplied values. -func LoadValues(data io.Reader) (map[string]interface{}, error) { - values := map[string]interface{}{} +func LoadValues(data io.Reader) (map[string]any, error) { + values := map[string]any{} reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) for { - currentMap := map[string]interface{}{} + currentMap := map[string]any{} raw, err := reader.Read() if err != nil { if errors.Is(err, io.EOF) { @@ -204,13 +204,13 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) { // MergeMaps merges two maps. If a key exists in both maps, the value from b will be used. // If the value is a map, the maps will be merged recursively. -func MergeMaps(a, b map[string]interface{}) map[string]interface{} { - out := make(map[string]interface{}, len(a)) +func MergeMaps(a, b map[string]any) map[string]any { + out := make(map[string]any, len(a)) maps.Copy(out, a) for k, v := range b { - if v, ok := v.(map[string]interface{}); ok { + if v, ok := v.(map[string]any); ok { if bv, ok := out[k]; ok { - if bv, ok := bv.(map[string]interface{}); ok { + if bv, ok := bv.(map[string]any); ok { out[k] = MergeMaps(bv, v) continue } diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index 12403f9c2..c32f79aff 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -455,7 +455,7 @@ func TestLoadInvalidArchive(t *testing.T) { func TestLoadValues(t *testing.T) { testCases := map[string]struct { data []byte - expctedValues map[string]interface{} + expctedValues map[string]any }{ "It should load values correctly": { data: []byte(` @@ -464,11 +464,11 @@ foo: bar: version: v2 `), - expctedValues: map[string]interface{}{ - "foo": map[string]interface{}{ + expctedValues: map[string]any{ + "foo": map[string]any{ "image": "foo:v1", }, - "bar": map[string]interface{}{ + "bar": map[string]any{ "version": "v2", }, }, @@ -483,11 +483,11 @@ bar: foo: image: foo:v2 `), - expctedValues: map[string]interface{}{ - "foo": map[string]interface{}{ + expctedValues: map[string]any{ + "foo": map[string]any{ "image": "foo:v2", }, - "bar": map[string]interface{}{ + "bar": map[string]any{ "version": "v2", }, }, @@ -507,24 +507,24 @@ foo: } func TestMergeValuesV3(t *testing.T) { - nestedMap := map[string]interface{}{ + nestedMap := map[string]any{ "foo": "bar", "baz": map[string]string{ "cool": "stuff", }, } - anotherNestedMap := map[string]interface{}{ + anotherNestedMap := map[string]any{ "foo": "bar", "baz": map[string]string{ "cool": "things", "awesome": "stuff", }, } - flatMap := map[string]interface{}{ + flatMap := map[string]any{ "foo": "bar", "baz": "stuff", } - anotherFlatMap := map[string]interface{}{ + anotherFlatMap := map[string]any{ "testing": "fun", } @@ -547,7 +547,7 @@ func TestMergeValuesV3(t *testing.T) { } testMap = MergeMaps(anotherFlatMap, anotherNestedMap) - expectedMap := map[string]interface{}{ + expectedMap := map[string]any{ "testing": "fun", "foo": "bar", "baz": map[string]string{ diff --git a/internal/chart/v3/util/dependencies.go b/internal/chart/v3/util/dependencies.go index 4ef9e6961..5bf8a593c 100644 --- a/internal/chart/v3/util/dependencies.go +++ b/internal/chart/v3/util/dependencies.go @@ -140,7 +140,7 @@ func copyMetadata(metadata *chart.Metadata) *chart.Metadata { } // processDependencyEnabled removes disabled charts from dependencies -func processDependencyEnabled(c *chart.Chart, v map[string]interface{}, path string) error { +func processDependencyEnabled(c *chart.Chart, v map[string]any, path string) error { if c.Metadata.Dependencies == nil { return nil } @@ -226,7 +226,7 @@ Loop: } // pathToMap creates a nested map given a YAML path in dot notation. -func pathToMap(path string, data map[string]interface{}) map[string]interface{} { +func pathToMap(path string, data map[string]any) map[string]any { if path == "." { return data } @@ -235,13 +235,13 @@ func pathToMap(path string, data map[string]interface{}) map[string]interface{} func parsePath(key string) []string { return strings.Split(key, ".") } -func set(path []string, data map[string]interface{}) map[string]interface{} { +func set(path []string, data map[string]any) map[string]any { if len(path) == 0 { return nil } cur := data for i := len(path) - 1; i >= 0; i-- { - cur = map[string]interface{}{path[i]: cur} + cur = map[string]any{path[i]: cur} } return cur } @@ -262,13 +262,13 @@ func processImportValues(c *chart.Chart, merge bool) error { if err != nil { return err } - b := make(map[string]interface{}) + b := make(map[string]any) // import values from each dependency if specified in import-values for _, r := range c.Metadata.Dependencies { - var outiv []interface{} + var outiv []any for _, riv := range r.ImportValues { switch iv := riv.(type) { - case map[string]interface{}: + case map[string]any: child := fmt.Sprintf("%v", iv["child"]) parent := fmt.Sprintf("%v", iv["parent"]) @@ -336,27 +336,27 @@ func processImportValues(c *chart.Chart, merge bool) error { return nil } -func deepCopyMap(vals map[string]interface{}) map[string]interface{} { +func deepCopyMap(vals map[string]any) map[string]any { valsCopy, err := copystructure.Copy(vals) if err != nil { return vals } - return valsCopy.(map[string]interface{}) + return valsCopy.(map[string]any) } -func trimNilValues(vals map[string]interface{}) map[string]interface{} { +func trimNilValues(vals map[string]any) map[string]any { valsCopy, err := copystructure.Copy(vals) if err != nil { return vals } - valsCopyMap := valsCopy.(map[string]interface{}) + valsCopyMap := valsCopy.(map[string]any) for key, val := range valsCopyMap { if val == nil { // Iterate over the values and remove nil keys delete(valsCopyMap, key) } else if istable(val) { // Recursively call into ourselves to remove keys from inner tables - valsCopyMap[key] = trimNilValues(val.(map[string]interface{})) + valsCopyMap[key] = trimNilValues(val.(map[string]any)) } } @@ -364,8 +364,8 @@ func trimNilValues(vals map[string]interface{}) map[string]interface{} { } // istable is a special-purpose function to see if the present thing matches the definition of a YAML table. -func istable(v interface{}) bool { - _, ok := v.(map[string]interface{}) +func istable(v any) bool { + _, ok := v.(map[string]any) return ok } From ba38159313d4f09280591ba7f860ef0523716220 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 6 Feb 2026 16:10:54 +0100 Subject: [PATCH 10/26] chore(pkg): fix modernize linter #### Description fix modernize linter in pkg/strvals Signed-off-by: Matthieu MOREL --- pkg/chart/common/util/coalesce.go | 62 +++---- pkg/chart/common/util/coalesce_test.go | 164 ++++++++--------- pkg/chart/v2/util/create.go | 2 +- pkg/strvals/literal_parser_test.go | 140 +++++++-------- pkg/strvals/parser_test.go | 232 ++++++++++++------------- 5 files changed, 300 insertions(+), 300 deletions(-) diff --git a/pkg/chart/common/util/coalesce.go b/pkg/chart/common/util/coalesce.go index 6c72b3d56..5994febbc 100644 --- a/pkg/chart/common/util/coalesce.go +++ b/pkg/chart/common/util/coalesce.go @@ -42,7 +42,7 @@ func concatPrefix(a, b string) string { // - Scalar values and arrays are replaced, maps are merged // - A chart has access to all of the variables for it, as well as all of // the values destined for its dependencies. -func CoalesceValues(chrt chart.Charter, vals map[string]interface{}) (common.Values, error) { +func CoalesceValues(chrt chart.Charter, vals map[string]any) (common.Values, error) { valsCopy, err := copyValues(vals) if err != nil { return vals, err @@ -64,7 +64,7 @@ func CoalesceValues(chrt chart.Charter, vals map[string]interface{}) (common.Val // Retaining Nils is useful when processes early in a Helm action or business // logic need to retain them for when Coalescing will happen again later in the // business logic. -func MergeValues(chrt chart.Charter, vals map[string]interface{}) (common.Values, error) { +func MergeValues(chrt chart.Charter, vals map[string]any) (common.Values, error) { valsCopy, err := copyValues(vals) if err != nil { return vals, err @@ -72,22 +72,22 @@ func MergeValues(chrt chart.Charter, vals map[string]interface{}) (common.Values return coalesce(log.Printf, chrt, valsCopy, "", true) } -func copyValues(vals map[string]interface{}) (common.Values, error) { +func copyValues(vals map[string]any) (common.Values, error) { v, err := copystructure.Copy(vals) if err != nil { return vals, err } - valsCopy := v.(map[string]interface{}) + valsCopy := v.(map[string]any) // if we have an empty map, make sure it is initialized if valsCopy == nil { - valsCopy = make(map[string]interface{}) + valsCopy = make(map[string]any) } return valsCopy, nil } -type printFn func(format string, v ...interface{}) +type printFn func(format string, v ...any) // coalesce coalesces the dest values and the chart values, giving priority to the dest values. // @@ -96,13 +96,13 @@ type printFn func(format string, v ...interface{}) // Note, the merge argument specifies whether this is being used by MergeValues // or CoalesceValues. Coalescing removes null values and their keys in some // situations while merging keeps the null values. -func coalesce(printf printFn, ch chart.Charter, dest map[string]interface{}, prefix string, merge bool) (map[string]interface{}, error) { +func coalesce(printf printFn, ch chart.Charter, dest map[string]any, prefix string, merge bool) (map[string]any, error) { coalesceValues(printf, ch, dest, prefix, merge) return coalesceDeps(printf, ch, dest, prefix, merge) } // coalesceDeps coalesces the dependencies of the given chart. -func coalesceDeps(printf printFn, chrt chart.Charter, dest map[string]interface{}, prefix string, merge bool) (map[string]interface{}, error) { +func coalesceDeps(printf printFn, chrt chart.Charter, dest map[string]any, prefix string, merge bool) (map[string]any, error) { ch, err := chart.NewAccessor(chrt) if err != nil { return dest, err @@ -114,12 +114,12 @@ func coalesceDeps(printf printFn, chrt chart.Charter, dest map[string]interface{ } if c, ok := dest[sub.Name()]; !ok { // If dest doesn't already have the key, create it. - dest[sub.Name()] = make(map[string]interface{}) + dest[sub.Name()] = make(map[string]any) } else if !istable(c) { return dest, fmt.Errorf("type mismatch on %s: %t", sub.Name(), c) } if dv, ok := dest[sub.Name()]; ok { - dvmap := dv.(map[string]interface{}) + dvmap := dv.(map[string]any) subPrefix := concatPrefix(prefix, ch.Name()) // Get globals out of dest and merge them into dvmap. coalesceGlobals(printf, dvmap, dest, subPrefix, merge) @@ -137,19 +137,19 @@ func coalesceDeps(printf printFn, chrt chart.Charter, dest map[string]interface{ // coalesceGlobals copies the globals out of src and merges them into dest. // // For convenience, returns dest. -func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix string, _ bool) { - var dg, sg map[string]interface{} +func coalesceGlobals(printf printFn, dest, src map[string]any, prefix string, _ bool) { + var dg, sg map[string]any if destglob, ok := dest[common.GlobalKey]; !ok { - dg = make(map[string]interface{}) - } else if dg, ok = destglob.(map[string]interface{}); !ok { + dg = make(map[string]any) + } else if dg, ok = destglob.(map[string]any); !ok { printf("warning: skipping globals because destination %s is not a table.", common.GlobalKey) return } if srcglob, ok := src[common.GlobalKey]; !ok { - sg = make(map[string]interface{}) - } else if sg, ok = srcglob.(map[string]interface{}); !ok { + sg = make(map[string]any) + } else if sg, ok = srcglob.(map[string]any); !ok { printf("warning: skipping globals because source %s is not a table.", common.GlobalKey) return } @@ -160,12 +160,12 @@ func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix st // tables in globals. for key, val := range sg { if istable(val) { - vv := copyMap(val.(map[string]interface{})) + vv := copyMap(val.(map[string]any)) if destv, ok := dg[key]; !ok { // Here there is no merge. We're just adding. dg[key] = vv } else { - if destvmap, ok := destv.(map[string]interface{}); !ok { + if destvmap, ok := destv.(map[string]any); !ok { printf("Conflict: cannot merge map onto non-map for %q. Skipping.", key) } else { // Basically, we reverse order of coalesce here to merge @@ -189,8 +189,8 @@ func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix st dest[common.GlobalKey] = dg } -func copyMap(src map[string]interface{}) map[string]interface{} { - m := make(map[string]interface{}, len(src)) +func copyMap(src map[string]any) map[string]any { + m := make(map[string]any, len(src)) maps.Copy(m, src) return m } @@ -198,7 +198,7 @@ func copyMap(src map[string]interface{}) map[string]interface{} { // coalesceValues builds up a values map for a particular chart. // // Values in v will override the values in the chart. -func coalesceValues(printf printFn, c chart.Charter, v map[string]interface{}, prefix string, merge bool) { +func coalesceValues(printf printFn, c chart.Charter, v map[string]any, prefix string, merge bool) { ch, err := chart.NewAccessor(c) if err != nil { return @@ -210,7 +210,7 @@ func coalesceValues(printf printFn, c chart.Charter, v map[string]interface{}, p // the original c.Values is altered. Creating a deep copy stops the problem. // This section is fault-tolerant as there is no ability to return an error. valuesCopy, err := copystructure.Copy(ch.Values()) - var vc map[string]interface{} + var vc map[string]any var ok bool if err != nil { // If there is an error something is wrong with copying c.Values it @@ -220,7 +220,7 @@ func coalesceValues(printf printFn, c chart.Charter, v map[string]interface{}, p printf("warning: unable to copy values, err: %s", err) vc = ch.Values() } else { - vc, ok = valuesCopy.(map[string]interface{}) + vc, ok = valuesCopy.(map[string]any) if !ok { // c.Values has a map[string]interface{} structure. If the copy of // it cannot be treated as map[string]interface{} there is something @@ -238,9 +238,9 @@ func coalesceValues(printf printFn, c chart.Charter, v map[string]interface{}, p // This allows Helm's various sources of values (value files or --set) to // remove incompatible keys from any previous chart, file, or set values. delete(v, key) - } else if dest, ok := value.(map[string]interface{}); ok { + } else if dest, ok := value.(map[string]any); ok { // if v[key] is a table, merge nv's val table into v[key]. - src, ok := val.(map[string]interface{}) + src, ok := val.(map[string]any) if !ok { // If the original value is nil, there is nothing to coalesce, so we don't print // the warning @@ -283,18 +283,18 @@ func childChartMergeTrue(chrt chart.Charter, key string, merge bool) bool { // CoalesceTables merges a source map into a destination map. // // dest is considered authoritative. -func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} { +func CoalesceTables(dst, src map[string]any) map[string]any { return coalesceTablesFullKey(log.Printf, dst, src, "", false) } -func MergeTables(dst, src map[string]interface{}) map[string]interface{} { +func MergeTables(dst, src map[string]any) map[string]any { return coalesceTablesFullKey(log.Printf, dst, src, "", true) } // coalesceTablesFullKey merges a source map into a destination map. // // dest is considered authoritative. -func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, prefix string, merge bool) map[string]interface{} { +func coalesceTablesFullKey(printf printFn, dst, src map[string]any, prefix string, merge bool) map[string]any { // When --reuse-values is set but there are no modifications yet, return new values if src == nil { return dst @@ -330,7 +330,7 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, pref dst[key] = val } else if istable(val) { if istable(dv) { - coalesceTablesFullKey(printf, dv.(map[string]interface{}), val.(map[string]interface{}), fullkey, merge) + coalesceTablesFullKey(printf, dv.(map[string]any), val.(map[string]any), fullkey, merge) } else { printf("warning: cannot overwrite table with non table for %s (%v)", fullkey, val) } @@ -342,7 +342,7 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, pref } // istable is a special-purpose function to see if the present thing matches the definition of a YAML table. -func istable(v interface{}) bool { - _, ok := v.(map[string]interface{}) +func istable(v any) bool { + _, ok := v.(map[string]any) return ok } diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index 4eaf4be2b..1d0baa84d 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -75,65 +75,65 @@ func TestCoalesceValues(t *testing.T) { c := withDeps(&chart.Chart{ Metadata: &chart.Metadata{Name: "moby"}, - Values: map[string]interface{}{ + Values: map[string]any{ "back": "exists", "bottom": "exists", "front": "exists", "left": "exists", "name": "moby", - "nested": map[string]interface{}{"boat": true}, + "nested": map[string]any{"boat": true}, "override": "bad", "right": "exists", "scope": "moby", "top": "nope", - "global": map[string]interface{}{ - "nested2": map[string]interface{}{"l0": "moby"}, + "global": map[string]any{ + "nested2": map[string]any{"l0": "moby"}, }, - "pequod": map[string]interface{}{ + "pequod": map[string]any{ "boat": "maybe", - "ahab": map[string]interface{}{ + "ahab": map[string]any{ "boat": "maybe", - "nested": map[string]interface{}{"boat": "maybe"}, + "nested": map[string]any{"boat": "maybe"}, }, }, }, }, withDeps(&chart.Chart{ Metadata: &chart.Metadata{Name: "pequod"}, - Values: map[string]interface{}{ + Values: map[string]any{ "name": "pequod", "scope": "pequod", - "global": map[string]interface{}{ - "nested2": map[string]interface{}{"l1": "pequod"}, + "global": map[string]any{ + "nested2": map[string]any{"l1": "pequod"}, }, "boat": false, - "ahab": map[string]interface{}{ + "ahab": map[string]any{ "boat": false, - "nested": map[string]interface{}{"boat": false}, + "nested": map[string]any{"boat": false}, }, }, }, &chart.Chart{ Metadata: &chart.Metadata{Name: "ahab"}, - Values: map[string]interface{}{ - "global": map[string]interface{}{ - "nested": map[string]interface{}{"foo": "bar", "foo2": "bar2"}, - "nested2": map[string]interface{}{"l2": "ahab"}, + Values: map[string]any{ + "global": map[string]any{ + "nested": map[string]any{"foo": "bar", "foo2": "bar2"}, + "nested2": map[string]any{"l2": "ahab"}, }, "scope": "ahab", "name": "ahab", "boat": true, - "nested": map[string]interface{}{"foo": false, "boat": true}, - "object": map[string]interface{}{"foo": "bar"}, + "nested": map[string]any{"foo": false, "boat": true}, + "object": map[string]any{"foo": "bar"}, }, }, ), &chart.Chart{ Metadata: &chart.Metadata{Name: "spouter"}, - Values: map[string]interface{}{ + Values: map[string]any{ "scope": "spouter", - "global": map[string]interface{}{ - "nested2": map[string]interface{}{"l1": "spouter"}, + "global": map[string]any{ + "nested2": map[string]any{"l1": "spouter"}, }, }, }, @@ -215,21 +215,21 @@ func TestCoalesceValues(t *testing.T) { } } - if _, ok := v["nested"].(map[string]interface{})["boat"]; ok { + if _, ok := v["nested"].(map[string]any)["boat"]; ok { t.Error("Expected nested boat key to be removed, still present") } - subchart := v["pequod"].(map[string]interface{}) + subchart := v["pequod"].(map[string]any) if _, ok := subchart["boat"]; ok { t.Error("Expected subchart boat key to be removed, still present") } - subsubchart := subchart["ahab"].(map[string]interface{}) + subsubchart := subchart["ahab"].(map[string]any) if _, ok := subsubchart["boat"]; ok { t.Error("Expected sub-subchart ahab boat key to be removed, still present") } - if _, ok := subsubchart["nested"].(map[string]interface{})["boat"]; ok { + if _, ok := subsubchart["nested"].(map[string]any)["boat"]; ok { t.Error("Expected sub-subchart nested boat key to be removed, still present") } @@ -241,7 +241,7 @@ func TestCoalesceValues(t *testing.T) { is.Equal(valsCopy, vals) } -func ttpl(tpl string, v map[string]interface{}) (string, error) { +func ttpl(tpl string, v map[string]any) (string, error) { var b bytes.Buffer tt := template.Must(template.New("t").Parse(tpl)) err := tt.Execute(&b, v) @@ -253,52 +253,52 @@ func TestMergeValues(t *testing.T) { c := withDeps(&chart.Chart{ Metadata: &chart.Metadata{Name: "moby"}, - Values: map[string]interface{}{ + Values: map[string]any{ "back": "exists", "bottom": "exists", "front": "exists", "left": "exists", "name": "moby", - "nested": map[string]interface{}{"boat": true}, + "nested": map[string]any{"boat": true}, "override": "bad", "right": "exists", "scope": "moby", "top": "nope", - "global": map[string]interface{}{ - "nested2": map[string]interface{}{"l0": "moby"}, + "global": map[string]any{ + "nested2": map[string]any{"l0": "moby"}, }, }, }, withDeps(&chart.Chart{ Metadata: &chart.Metadata{Name: "pequod"}, - Values: map[string]interface{}{ + Values: map[string]any{ "name": "pequod", "scope": "pequod", - "global": map[string]interface{}{ - "nested2": map[string]interface{}{"l1": "pequod"}, + "global": map[string]any{ + "nested2": map[string]any{"l1": "pequod"}, }, }, }, &chart.Chart{ Metadata: &chart.Metadata{Name: "ahab"}, - Values: map[string]interface{}{ - "global": map[string]interface{}{ - "nested": map[string]interface{}{"foo": "bar"}, - "nested2": map[string]interface{}{"l2": "ahab"}, + Values: map[string]any{ + "global": map[string]any{ + "nested": map[string]any{"foo": "bar"}, + "nested2": map[string]any{"l2": "ahab"}, }, "scope": "ahab", "name": "ahab", "boat": true, - "nested": map[string]interface{}{"foo": false, "bar": true}, + "nested": map[string]any{"foo": false, "bar": true}, }, }, ), &chart.Chart{ Metadata: &chart.Metadata{Name: "spouter"}, - Values: map[string]interface{}{ + Values: map[string]any{ "scope": "spouter", - "global": map[string]interface{}{ - "nested2": map[string]interface{}{"l1": "spouter"}, + "global": map[string]any{ + "nested2": map[string]any{"l1": "spouter"}, }, }, }, @@ -383,16 +383,16 @@ func TestMergeValues(t *testing.T) { } } - if _, ok := v["nested"].(map[string]interface{})["boat"]; !ok { + if _, ok := v["nested"].(map[string]any)["boat"]; !ok { t.Error("Expected nested boat key to be present but it was removed") } - subchart := v["pequod"].(map[string]interface{})["ahab"].(map[string]interface{}) + subchart := v["pequod"].(map[string]any)["ahab"].(map[string]any) if _, ok := subchart["boat"]; !ok { t.Error("Expected subchart boat key to be present but it was removed") } - if _, ok := subchart["nested"].(map[string]interface{})["bar"]; !ok { + if _, ok := subchart["nested"].(map[string]any)["bar"]; !ok { t.Error("Expected subchart nested bar key to be present but it was removed") } @@ -401,28 +401,28 @@ func TestMergeValues(t *testing.T) { } func TestCoalesceTables(t *testing.T) { - dst := map[string]interface{}{ + dst := map[string]any{ "name": "Ishmael", - "address": map[string]interface{}{ + "address": map[string]any{ "street": "123 Spouter Inn Ct.", "city": "Nantucket", "country": nil, }, - "details": map[string]interface{}{ + "details": map[string]any{ "friends": []string{"Tashtego"}, }, "boat": "pequod", "hole": nil, } - src := map[string]interface{}{ + src := map[string]any{ "occupation": "whaler", - "address": map[string]interface{}{ + "address": map[string]any{ "state": "MA", "street": "234 Spouter Inn Ct.", "country": "US", }, "details": "empty", - "boat": map[string]interface{}{ + "boat": map[string]any{ "mast": true, }, "hole": "black", @@ -439,7 +439,7 @@ func TestCoalesceTables(t *testing.T) { t.Errorf("Unexpected occupation: %s", dst["occupation"]) } - addr, ok := dst["address"].(map[string]interface{}) + addr, ok := dst["address"].(map[string]any) if !ok { t.Fatal("Address went away.") } @@ -460,7 +460,7 @@ func TestCoalesceTables(t *testing.T) { t.Error("The country is not left out.") } - if det, ok := dst["details"].(map[string]interface{}); !ok { + if det, ok := dst["details"].(map[string]any); !ok { t.Fatalf("Details is the wrong type: %v", dst["details"]) } else if _, ok := det["friends"]; !ok { t.Error("Could not find your friends. Maybe you don't have any. :-(") @@ -474,14 +474,14 @@ func TestCoalesceTables(t *testing.T) { t.Error("The hole still exists.") } - dst2 := map[string]interface{}{ + dst2 := map[string]any{ "name": "Ishmael", - "address": map[string]interface{}{ + "address": map[string]any{ "street": "123 Spouter Inn Ct.", "city": "Nantucket", "country": "US", }, - "details": map[string]interface{}{ + "details": map[string]any{ "friends": []string{"Tashtego"}, }, "boat": "pequod", @@ -496,7 +496,7 @@ func TestCoalesceTables(t *testing.T) { t.Errorf("Unexpected name: %s", dst2["name"]) } - addr2, ok := dst2["address"].(map[string]interface{}) + addr2, ok := dst2["address"].(map[string]any) if !ok { t.Fatal("Address went away.") } @@ -513,7 +513,7 @@ func TestCoalesceTables(t *testing.T) { t.Errorf("Unexpected Country: %v", addr2["country"]) } - if det2, ok := dst2["details"].(map[string]interface{}); !ok { + if det2, ok := dst2["details"].(map[string]any); !ok { t.Fatalf("Details is the wrong type: %v", dst2["details"]) } else if _, ok := det2["friends"]; !ok { t.Error("Could not find your friends. Maybe you don't have any. :-(") @@ -529,28 +529,28 @@ func TestCoalesceTables(t *testing.T) { } func TestMergeTables(t *testing.T) { - dst := map[string]interface{}{ + dst := map[string]any{ "name": "Ishmael", - "address": map[string]interface{}{ + "address": map[string]any{ "street": "123 Spouter Inn Ct.", "city": "Nantucket", "country": nil, }, - "details": map[string]interface{}{ + "details": map[string]any{ "friends": []string{"Tashtego"}, }, "boat": "pequod", "hole": nil, } - src := map[string]interface{}{ + src := map[string]any{ "occupation": "whaler", - "address": map[string]interface{}{ + "address": map[string]any{ "state": "MA", "street": "234 Spouter Inn Ct.", "country": "US", }, "details": "empty", - "boat": map[string]interface{}{ + "boat": map[string]any{ "mast": true, }, "hole": "black", @@ -567,7 +567,7 @@ func TestMergeTables(t *testing.T) { t.Errorf("Unexpected occupation: %s", dst["occupation"]) } - addr, ok := dst["address"].(map[string]interface{}) + addr, ok := dst["address"].(map[string]any) if !ok { t.Fatal("Address went away.") } @@ -590,7 +590,7 @@ func TestMergeTables(t *testing.T) { t.Error("The country is left out.") } - if det, ok := dst["details"].(map[string]interface{}); !ok { + if det, ok := dst["details"].(map[string]any); !ok { t.Fatalf("Details is the wrong type: %v", dst["details"]) } else if _, ok := det["friends"]; !ok { t.Error("Could not find your friends. Maybe you don't have any. :-(") @@ -606,14 +606,14 @@ func TestMergeTables(t *testing.T) { t.Error("The hole no longer exists.") } - dst2 := map[string]interface{}{ + dst2 := map[string]any{ "name": "Ishmael", - "address": map[string]interface{}{ + "address": map[string]any{ "street": "123 Spouter Inn Ct.", "city": "Nantucket", "country": "US", }, - "details": map[string]interface{}{ + "details": map[string]any{ "friends": []string{"Tashtego"}, }, "boat": "pequod", @@ -629,7 +629,7 @@ func TestMergeTables(t *testing.T) { t.Errorf("Unexpected name: %s", dst2["name"]) } - addr2, ok := dst2["address"].(map[string]interface{}) + addr2, ok := dst2["address"].(map[string]any) if !ok { t.Fatal("Address went away.") } @@ -646,7 +646,7 @@ func TestMergeTables(t *testing.T) { t.Errorf("Unexpected Country: %v", addr2["country"]) } - if det2, ok := dst2["details"].(map[string]interface{}); !ok { + if det2, ok := dst2["details"].(map[string]any); !ok { t.Fatalf("Details is the wrong type: %v", dst2["details"]) } else if _, ok := det2["friends"]; !ok { t.Error("Could not find your friends. Maybe you don't have any. :-(") @@ -669,24 +669,24 @@ func TestCoalesceValuesWarnings(t *testing.T) { c := withDeps(&chart.Chart{ Metadata: &chart.Metadata{Name: "level1"}, - Values: map[string]interface{}{ + Values: map[string]any{ "name": "moby", }, }, withDeps(&chart.Chart{ Metadata: &chart.Metadata{Name: "level2"}, - Values: map[string]interface{}{ + Values: map[string]any{ "name": "pequod", }, }, &chart.Chart{ Metadata: &chart.Metadata{Name: "level3"}, - Values: map[string]interface{}{ + Values: map[string]any{ "name": "ahab", "boat": true, - "spear": map[string]interface{}{ + "spear": map[string]any{ "tip": true, - "sail": map[string]interface{}{ + "sail": map[string]any{ "cotton": true, }, }, @@ -695,12 +695,12 @@ func TestCoalesceValuesWarnings(t *testing.T) { ), ) - vals := map[string]interface{}{ - "level2": map[string]interface{}{ - "level3": map[string]interface{}{ - "boat": map[string]interface{}{"mast": true}, - "spear": map[string]interface{}{ - "tip": map[string]interface{}{ + vals := map[string]any{ + "level2": map[string]any{ + "level3": map[string]any{ + "boat": map[string]any{"mast": true}, + "spear": map[string]any{ + "tip": map[string]any{ "sharp": true, }, "sail": true, @@ -710,7 +710,7 @@ func TestCoalesceValuesWarnings(t *testing.T) { } warnings := make([]string, 0) - printf := func(format string, v ...interface{}) { + printf := func(format string, v ...any) { t.Logf(format, v...) warnings = append(warnings, fmt.Sprintf(format, v...)) } diff --git a/pkg/chart/v2/util/create.go b/pkg/chart/v2/util/create.go index bf572c707..0d7ae8d5c 100644 --- a/pkg/chart/v2/util/create.go +++ b/pkg/chart/v2/util/create.go @@ -669,7 +669,7 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error { return fmt.Errorf("reading values file: %w", err) } - var m map[string]interface{} + var m map[string]any if err := yaml.Unmarshal(transform(string(b), schart.Name()), &m); err != nil { return fmt.Errorf("transforming values file: %w", err) } diff --git a/pkg/strvals/literal_parser_test.go b/pkg/strvals/literal_parser_test.go index 6a76458f5..cc3d73d50 100644 --- a/pkg/strvals/literal_parser_test.go +++ b/pkg/strvals/literal_parser_test.go @@ -26,7 +26,7 @@ import ( func TestParseLiteral(t *testing.T) { cases := []struct { str string - expect map[string]interface{} + expect map[string]any err bool }{ { @@ -35,61 +35,61 @@ func TestParseLiteral(t *testing.T) { }, { str: "name=", - expect: map[string]interface{}{"name": ""}, + expect: map[string]any{"name": ""}, }, { str: "name=value", - expect: map[string]interface{}{"name": "value"}, + expect: map[string]any{"name": "value"}, err: false, }, { str: "long_int_string=1234567890", - expect: map[string]interface{}{"long_int_string": "1234567890"}, + expect: map[string]any{"long_int_string": "1234567890"}, err: false, }, { str: "boolean=true", - expect: map[string]interface{}{"boolean": "true"}, + expect: map[string]any{"boolean": "true"}, err: false, }, { str: "is_null=null", - expect: map[string]interface{}{"is_null": "null"}, + expect: map[string]any{"is_null": "null"}, err: false, }, { str: "zero=0", - expect: map[string]interface{}{"zero": "0"}, + expect: map[string]any{"zero": "0"}, err: false, }, { str: "name1=null,name2=value2", - expect: map[string]interface{}{"name1": "null,name2=value2"}, + expect: map[string]any{"name1": "null,name2=value2"}, err: false, }, { str: "name1=value,,,tail", - expect: map[string]interface{}{"name1": "value,,,tail"}, + expect: map[string]any{"name1": "value,,,tail"}, err: false, }, { str: "leading_zeros=00009", - expect: map[string]interface{}{"leading_zeros": "00009"}, + expect: map[string]any{"leading_zeros": "00009"}, err: false, }, { str: "name=one two three", - expect: map[string]interface{}{"name": "one two three"}, + expect: map[string]any{"name": "one two three"}, err: false, }, { str: "outer.inner=value", - expect: map[string]interface{}{"outer": map[string]interface{}{"inner": "value"}}, + expect: map[string]any{"outer": map[string]any{"inner": "value"}}, err: false, }, { str: "outer.middle.inner=value", - expect: map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}}, + expect: map[string]any{"outer": map[string]any{"middle": map[string]any{"inner": "value"}}}, err: false, }, { @@ -98,7 +98,7 @@ func TestParseLiteral(t *testing.T) { }, { str: "name1.name2=", - expect: map[string]interface{}{"name1": map[string]interface{}{"name2": ""}}, + expect: map[string]any{"name1": map[string]any{"name2": ""}}, err: false, }, { @@ -111,20 +111,20 @@ func TestParseLiteral(t *testing.T) { }, { str: "name1={value1,value2}", - expect: map[string]interface{}{"name1": "{value1,value2}"}, + expect: map[string]any{"name1": "{value1,value2}"}, }, // List support { str: "list[0]=foo", - expect: map[string]interface{}{"list": []string{"foo"}}, + expect: map[string]any{"list": []string{"foo"}}, err: false, }, { str: "list[0].foo=bar", - expect: map[string]interface{}{ - "list": []interface{}{ - map[string]interface{}{"foo": "bar"}, + expect: map[string]any{ + "list": []any{ + map[string]any{"foo": "bar"}, }, }, err: false, @@ -135,7 +135,7 @@ func TestParseLiteral(t *testing.T) { }, { str: "list[3]=bar", - expect: map[string]interface{}{"list": []interface{}{nil, nil, nil, "bar"}}, + expect: map[string]any{"list": []any{nil, nil, nil, "bar"}}, err: false, }, { @@ -144,133 +144,133 @@ func TestParseLiteral(t *testing.T) { }, { str: "noval[0]", - expect: map[string]interface{}{"noval": []interface{}{}}, + expect: map[string]any{"noval": []any{}}, err: false, }, { str: "noval[0]=", - expect: map[string]interface{}{"noval": []interface{}{""}}, + expect: map[string]any{"noval": []any{""}}, err: false, }, { str: "nested[0][0]=1", - expect: map[string]interface{}{"nested": []interface{}{[]interface{}{"1"}}}, + expect: map[string]any{"nested": []any{[]any{"1"}}}, err: false, }, { str: "nested[1][1]=1", - expect: map[string]interface{}{"nested": []interface{}{nil, []interface{}{nil, "1"}}}, + expect: map[string]any{"nested": []any{nil, []any{nil, "1"}}}, err: false, }, { str: "name1.name2[0].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{{"foo": "bar"}}, + expect: map[string]any{ + "name1": map[string]any{ + "name2": []map[string]any{{"foo": "bar"}}, }, }, }, { str: "name1.name2[1].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{nil, {"foo": "bar"}}, + expect: map[string]any{ + "name1": map[string]any{ + "name2": []map[string]any{nil, {"foo": "bar"}}, }, }, }, { str: "name1.name2[1].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{nil, {"foo": "bar"}}, + expect: map[string]any{ + "name1": map[string]any{ + "name2": []map[string]any{nil, {"foo": "bar"}}, }, }, }, { str: "]={}].", - expect: map[string]interface{}{"]": "{}]."}, + expect: map[string]any{"]": "{}]."}, err: false, }, // issue test cases: , = $ ( ) { } . \ \\ { str: "name=val,val", - expect: map[string]interface{}{"name": "val,val"}, + expect: map[string]any{"name": "val,val"}, err: false, }, { str: "name=val.val", - expect: map[string]interface{}{"name": "val.val"}, + expect: map[string]any{"name": "val.val"}, err: false, }, { str: "name=val=val", - expect: map[string]interface{}{"name": "val=val"}, + expect: map[string]any{"name": "val=val"}, err: false, }, { str: "name=val$val", - expect: map[string]interface{}{"name": "val$val"}, + expect: map[string]any{"name": "val$val"}, err: false, }, { str: "name=(value", - expect: map[string]interface{}{"name": "(value"}, + expect: map[string]any{"name": "(value"}, err: false, }, { str: "name=value)", - expect: map[string]interface{}{"name": "value)"}, + expect: map[string]any{"name": "value)"}, err: false, }, { str: "name=(value)", - expect: map[string]interface{}{"name": "(value)"}, + expect: map[string]any{"name": "(value)"}, err: false, }, { str: "name={value", - expect: map[string]interface{}{"name": "{value"}, + expect: map[string]any{"name": "{value"}, err: false, }, { str: "name=value}", - expect: map[string]interface{}{"name": "value}"}, + expect: map[string]any{"name": "value}"}, err: false, }, { str: "name={value}", - expect: map[string]interface{}{"name": "{value}"}, + expect: map[string]any{"name": "{value}"}, err: false, }, { str: "name={value1,value2}", - expect: map[string]interface{}{"name": "{value1,value2}"}, + expect: map[string]any{"name": "{value1,value2}"}, err: false, }, { str: `name=val\val`, - expect: map[string]interface{}{"name": `val\val`}, + expect: map[string]any{"name": `val\val`}, err: false, }, { str: `name=val\\val`, - expect: map[string]interface{}{"name": `val\\val`}, + expect: map[string]any{"name": `val\\val`}, err: false, }, { str: `name=val\\\val`, - expect: map[string]interface{}{"name": `val\\\val`}, + expect: map[string]any{"name": `val\\\val`}, err: false, }, { str: `name={val,.?*v\0a!l)some`, - expect: map[string]interface{}{"name": `{val,.?*v\0a!l)some`}, + expect: map[string]any{"name": `{val,.?*v\0a!l)some`}, err: false, }, { str: `name=em%GT)tqUDqz,i-\h+Mbqs-!:.m\\rE=mkbM#rR}@{-k@`, - expect: map[string]interface{}{"name": `em%GT)tqUDqz,i-\h+Mbqs-!:.m\\rE=mkbM#rR}@{-k@`}, + expect: map[string]any{"name": `em%GT)tqUDqz,i-\h+Mbqs-!:.m\\rE=mkbM#rR}@{-k@`}, }, } @@ -307,20 +307,20 @@ func TestParseLiteralInto(t *testing.T) { tests := []struct { input string input2 string - got map[string]interface{} - expect map[string]interface{} + got map[string]any + expect map[string]any err bool }{ { input: "outer.inner1=value1,outer.inner3=value3,outer.inner4=4", - got: map[string]interface{}{ - "outer": map[string]interface{}{ + got: map[string]any{ + "outer": map[string]any{ "inner1": "overwrite", "inner2": "value2", }, }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ + expect: map[string]any{ + "outer": map[string]any{ "inner1": "value1,outer.inner3=value3,outer.inner4=4", "inner2": "value2", }}, @@ -329,9 +329,9 @@ func TestParseLiteralInto(t *testing.T) { { input: "listOuter[0][0].type=listValue", input2: "listOuter[0][0].status=alive", - got: map[string]interface{}{}, - expect: map[string]interface{}{ - "listOuter": [][]interface{}{{map[string]string{ + got: map[string]any{}, + expect: map[string]any{ + "listOuter": [][]any{{map[string]string{ "type": "listValue", "status": "alive", }}}, @@ -341,9 +341,9 @@ func TestParseLiteralInto(t *testing.T) { { input: "listOuter[0][0].type=listValue", input2: "listOuter[1][0].status=alive", - got: map[string]interface{}{}, - expect: map[string]interface{}{ - "listOuter": [][]interface{}{ + got: map[string]any{}, + expect: map[string]any{ + "listOuter": [][]any{ { map[string]string{"type": "listValue"}, }, @@ -357,17 +357,17 @@ func TestParseLiteralInto(t *testing.T) { { input: "listOuter[0][1][0].type=listValue", input2: "listOuter[0][0][1].status=alive", - got: map[string]interface{}{ - "listOuter": []interface{}{ - []interface{}{ - []interface{}{ + got: map[string]any{ + "listOuter": []any{ + []any{ + []any{ map[string]string{"exited": "old"}, }, }, }, }, - expect: map[string]interface{}{ - "listOuter": [][][]interface{}{ + expect: map[string]any{ + "listOuter": [][][]any{ { { map[string]string{"exited": "old"}, @@ -429,13 +429,13 @@ func TestParseLiteralNestedLevels(t *testing.T) { tests := []struct { str string - expect map[string]interface{} + expect map[string]any err bool errStr string }{ { "outer.middle.inner=value", - map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}}, + map[string]any{"outer": map[string]any{"middle": map[string]any{"inner": "value"}}}, false, "", }, diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go index 73403fc52..e3f8b9d8e 100644 --- a/pkg/strvals/parser_test.go +++ b/pkg/strvals/parser_test.go @@ -26,48 +26,48 @@ import ( func TestSetIndex(t *testing.T) { tests := []struct { name string - initial []interface{} - expect []interface{} + initial []any + expect []any add int val int err bool }{ { name: "short", - initial: []interface{}{0, 1}, - expect: []interface{}{0, 1, 2}, + initial: []any{0, 1}, + expect: []any{0, 1, 2}, add: 2, val: 2, err: false, }, { name: "equal", - initial: []interface{}{0, 1}, - expect: []interface{}{0, 2}, + initial: []any{0, 1}, + expect: []any{0, 2}, add: 1, val: 2, err: false, }, { name: "long", - initial: []interface{}{0, 1, 2, 3, 4, 5}, - expect: []interface{}{0, 1, 2, 4, 4, 5}, + initial: []any{0, 1, 2, 3, 4, 5}, + expect: []any{0, 1, 2, 4, 4, 5}, add: 3, val: 4, err: false, }, { name: "negative", - initial: []interface{}{0, 1, 2, 3, 4, 5}, - expect: []interface{}{0, 1, 2, 3, 4, 5}, + initial: []any{0, 1, 2, 3, 4, 5}, + expect: []any{0, 1, 2, 3, 4, 5}, add: -1, val: 4, err: true, }, { name: "large", - initial: []interface{}{0, 1, 2, 3, 4, 5}, - expect: []interface{}{0, 1, 2, 3, 4, 5}, + initial: []any{0, 1, 2, 3, 4, 5}, + expect: []any{0, 1, 2, 3, 4, 5}, add: MaxIndex + 1, val: 4, err: true, @@ -104,53 +104,53 @@ func TestSetIndex(t *testing.T) { func TestParseSet(t *testing.T) { testsString := []struct { str string - expect map[string]interface{} + expect map[string]any err bool }{ { str: "long_int_string=1234567890", - expect: map[string]interface{}{"long_int_string": "1234567890"}, + expect: map[string]any{"long_int_string": "1234567890"}, err: false, }, { str: "boolean=true", - expect: map[string]interface{}{"boolean": "true"}, + expect: map[string]any{"boolean": "true"}, err: false, }, { str: "is_null=null", - expect: map[string]interface{}{"is_null": "null"}, + expect: map[string]any{"is_null": "null"}, err: false, }, { str: "zero=0", - expect: map[string]interface{}{"zero": "0"}, + expect: map[string]any{"zero": "0"}, err: false, }, } tests := []struct { str string - expect map[string]interface{} + expect map[string]any err bool }{ { "name1=null,f=false,t=true", - map[string]interface{}{"name1": nil, "f": false, "t": true}, + map[string]any{"name1": nil, "f": false, "t": true}, false, }, { "name1=value1", - map[string]interface{}{"name1": "value1"}, + map[string]any{"name1": "value1"}, false, }, { "name1=value1,name2=value2", - map[string]interface{}{"name1": "value1", "name2": "value2"}, + map[string]any{"name1": "value1", "name2": "value2"}, false, }, { "name1=value1,name2=value2,", - map[string]interface{}{"name1": "value1", "name2": "value2"}, + map[string]any{"name1": "value1", "name2": "value2"}, false, }, { @@ -159,27 +159,27 @@ func TestParseSet(t *testing.T) { }, { str: "name1=,name2=value2", - expect: map[string]interface{}{"name1": "", "name2": "value2"}, + expect: map[string]any{"name1": "", "name2": "value2"}, }, { str: "leading_zeros=00009", - expect: map[string]interface{}{"leading_zeros": "00009"}, + expect: map[string]any{"leading_zeros": "00009"}, }, { str: "zero_int=0", - expect: map[string]interface{}{"zero_int": 0}, + expect: map[string]any{"zero_int": 0}, }, { str: "long_int=1234567890", - expect: map[string]interface{}{"long_int": 1234567890}, + expect: map[string]any{"long_int": 1234567890}, }, { str: "boolean=true", - expect: map[string]interface{}{"boolean": true}, + expect: map[string]any{"boolean": true}, }, { str: "is_null=null", - expect: map[string]interface{}{"is_null": nil}, + expect: map[string]any{"is_null": nil}, err: false, }, { @@ -200,40 +200,40 @@ func TestParseSet(t *testing.T) { }, { "name1=one\\,two,name2=three\\,four", - map[string]interface{}{"name1": "one,two", "name2": "three,four"}, + map[string]any{"name1": "one,two", "name2": "three,four"}, false, }, { "name1=one\\=two,name2=three\\=four", - map[string]interface{}{"name1": "one=two", "name2": "three=four"}, + map[string]any{"name1": "one=two", "name2": "three=four"}, false, }, { "name1=one two three,name2=three two one", - map[string]interface{}{"name1": "one two three", "name2": "three two one"}, + map[string]any{"name1": "one two three", "name2": "three two one"}, false, }, { "outer.inner=value", - map[string]interface{}{"outer": map[string]interface{}{"inner": "value"}}, + map[string]any{"outer": map[string]any{"inner": "value"}}, false, }, { "outer.middle.inner=value", - map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}}, + map[string]any{"outer": map[string]any{"middle": map[string]any{"inner": "value"}}}, false, }, { "outer.inner1=value,outer.inner2=value2", - map[string]interface{}{"outer": map[string]interface{}{"inner1": "value", "inner2": "value2"}}, + map[string]any{"outer": map[string]any{"inner1": "value", "inner2": "value2"}}, false, }, { "outer.inner1=value,outer.middle.inner=value", - map[string]interface{}{ - "outer": map[string]interface{}{ + map[string]any{ + "outer": map[string]any{ "inner1": "value", - "middle": map[string]interface{}{ + "middle": map[string]any{ "inner": "value", }, }, @@ -250,7 +250,7 @@ func TestParseSet(t *testing.T) { }, { str: "name1.name2=", - expect: map[string]interface{}{"name1": map[string]interface{}{"name2": ""}}, + expect: map[string]any{"name1": map[string]any{"name2": ""}}, }, { str: "name1.=name2", @@ -262,12 +262,12 @@ func TestParseSet(t *testing.T) { }, { "name1={value1,value2}", - map[string]interface{}{"name1": []string{"value1", "value2"}}, + map[string]any{"name1": []string{"value1", "value2"}}, false, }, { "name1={value1,value2},name2={value1,value2}", - map[string]interface{}{ + map[string]any{ "name1": []string{"value1", "value2"}, "name2": []string{"value1", "value2"}, }, @@ -275,12 +275,12 @@ func TestParseSet(t *testing.T) { }, { "name1={1021,902}", - map[string]interface{}{"name1": []int{1021, 902}}, + map[string]any{"name1": []int{1021, 902}}, false, }, { "name1.name2={value1,value2}", - map[string]interface{}{"name1": map[string]interface{}{"name2": []string{"value1", "value2"}}}, + map[string]any{"name1": map[string]any{"name2": []string{"value1", "value2"}}}, false, }, { @@ -290,21 +290,21 @@ func TestParseSet(t *testing.T) { // List support { str: "list[0]=foo", - expect: map[string]interface{}{"list": []string{"foo"}}, + expect: map[string]any{"list": []string{"foo"}}, }, { str: "list[0].foo=bar", - expect: map[string]interface{}{ - "list": []interface{}{ - map[string]interface{}{"foo": "bar"}, + expect: map[string]any{ + "list": []any{ + map[string]any{"foo": "bar"}, }, }, }, { str: "list[0].foo=bar,list[0].hello=world", - expect: map[string]interface{}{ - "list": []interface{}{ - map[string]interface{}{"foo": "bar", "hello": "world"}, + expect: map[string]any{ + "list": []any{ + map[string]any{"foo": "bar", "hello": "world"}, }, }, }, @@ -314,15 +314,15 @@ func TestParseSet(t *testing.T) { }, { str: "list[0]=foo,list[1]=bar", - expect: map[string]interface{}{"list": []string{"foo", "bar"}}, + expect: map[string]any{"list": []string{"foo", "bar"}}, }, { str: "list[0]=foo,list[1]=bar,", - expect: map[string]interface{}{"list": []string{"foo", "bar"}}, + expect: map[string]any{"list": []string{"foo", "bar"}}, }, { str: "list[0]=foo,list[3]=bar", - expect: map[string]interface{}{"list": []interface{}{"foo", nil, nil, "bar"}}, + expect: map[string]any{"list": []any{"foo", nil, nil, "bar"}}, }, { str: "list[0]=foo,list[-20]=bar", @@ -334,41 +334,41 @@ func TestParseSet(t *testing.T) { }, { str: "noval[0]", - expect: map[string]interface{}{"noval": []interface{}{}}, + expect: map[string]any{"noval": []any{}}, }, { str: "noval[0]=", - expect: map[string]interface{}{"noval": []interface{}{""}}, + expect: map[string]any{"noval": []any{""}}, }, { str: "nested[0][0]=1", - expect: map[string]interface{}{"nested": []interface{}{[]interface{}{1}}}, + expect: map[string]any{"nested": []any{[]any{1}}}, }, { str: "nested[1][1]=1", - expect: map[string]interface{}{"nested": []interface{}{nil, []interface{}{nil, 1}}}, + expect: map[string]any{"nested": []any{nil, []any{nil, 1}}}, }, { str: "name1.name2[0].foo=bar,name1.name2[1].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{{"foo": "bar"}, {"foo": "bar"}}, + expect: map[string]any{ + "name1": map[string]any{ + "name2": []map[string]any{{"foo": "bar"}, {"foo": "bar"}}, }, }, }, { str: "name1.name2[1].foo=bar,name1.name2[0].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{{"foo": "bar"}, {"foo": "bar"}}, + expect: map[string]any{ + "name1": map[string]any{ + "name2": []map[string]any{{"foo": "bar"}, {"foo": "bar"}}, }, }, }, { str: "name1.name2[1].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{nil, {"foo": "bar"}}, + expect: map[string]any{ + "name1": map[string]any{ + "name2": []map[string]any{nil, {"foo": "bar"}}, }, }, }, @@ -434,20 +434,20 @@ func TestParseInto(t *testing.T) { tests := []struct { input string input2 string - got map[string]interface{} - expect map[string]interface{} + got map[string]any + expect map[string]any err bool }{ { input: "outer.inner1=value1,outer.inner3=value3,outer.inner4=4", - got: map[string]interface{}{ - "outer": map[string]interface{}{ + got: map[string]any{ + "outer": map[string]any{ "inner1": "overwrite", "inner2": "value2", }, }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ + expect: map[string]any{ + "outer": map[string]any{ "inner1": "value1", "inner2": "value2", "inner3": "value3", @@ -458,9 +458,9 @@ func TestParseInto(t *testing.T) { { input: "listOuter[0][0].type=listValue", input2: "listOuter[0][0].status=alive", - got: map[string]interface{}{}, - expect: map[string]interface{}{ - "listOuter": [][]interface{}{{map[string]string{ + got: map[string]any{}, + expect: map[string]any{ + "listOuter": [][]any{{map[string]string{ "type": "listValue", "status": "alive", }}}, @@ -470,9 +470,9 @@ func TestParseInto(t *testing.T) { { input: "listOuter[0][0].type=listValue", input2: "listOuter[1][0].status=alive", - got: map[string]interface{}{}, - expect: map[string]interface{}{ - "listOuter": [][]interface{}{ + got: map[string]any{}, + expect: map[string]any{ + "listOuter": [][]any{ { map[string]string{"type": "listValue"}, }, @@ -486,17 +486,17 @@ func TestParseInto(t *testing.T) { { input: "listOuter[0][1][0].type=listValue", input2: "listOuter[0][0][1].status=alive", - got: map[string]interface{}{ - "listOuter": []interface{}{ - []interface{}{ - []interface{}{ + got: map[string]any{ + "listOuter": []any{ + []any{ + []any{ map[string]string{"exited": "old"}, }, }, }, }, - expect: map[string]interface{}{ - "listOuter": [][][]interface{}{ + expect: map[string]any{ + "listOuter": [][][]any{ { { map[string]string{"exited": "old"}, @@ -544,15 +544,15 @@ func TestParseInto(t *testing.T) { } func TestParseIntoString(t *testing.T) { - got := map[string]interface{}{ - "outer": map[string]interface{}{ + got := map[string]any{ + "outer": map[string]any{ "inner1": "overwrite", "inner2": "value2", }, } input := "outer.inner1=1,outer.inner3=3" - expect := map[string]interface{}{ - "outer": map[string]interface{}{ + expect := map[string]any{ + "outer": map[string]any{ "inner1": "1", "inner2": "value2", "inner3": "3", @@ -580,20 +580,20 @@ func TestParseIntoString(t *testing.T) { func TestParseJSON(t *testing.T) { tests := []struct { input string - got map[string]interface{} - expect map[string]interface{} + got map[string]any + expect map[string]any err bool }{ { // set json scalars values, and replace one existing key input: "outer.inner1=\"1\",outer.inner3=3,outer.inner4=true,outer.inner5=\"true\"", - got: map[string]interface{}{ - "outer": map[string]interface{}{ + got: map[string]any{ + "outer": map[string]any{ "inner1": "overwrite", "inner2": "value2", }, }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ + expect: map[string]any{ + "outer": map[string]any{ "inner1": "1", "inner2": "value2", "inner3": 3, @@ -605,43 +605,43 @@ func TestParseJSON(t *testing.T) { }, { // set json objects and arrays, and replace one existing key input: "outer.inner1={\"a\":\"1\",\"b\":2,\"c\":[1,2,3]},outer.inner3=[\"new value 1\",\"new value 2\"],outer.inner4={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner5=[{\"A\":\"1\",\"B\":2,\"C\":[1,2,3]}]", - got: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": map[string]interface{}{ + got: map[string]any{ + "outer": map[string]any{ + "inner1": map[string]any{ "x": "overwrite", }, "inner2": "value2", - "inner3": []interface{}{ + "inner3": []any{ "overwrite", }, }, }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": map[string]interface{}{"a": "1", "b": 2, "c": []interface{}{1, 2, 3}}, + expect: map[string]any{ + "outer": map[string]any{ + "inner1": map[string]any{"a": "1", "b": 2, "c": []any{1, 2, 3}}, "inner2": "value2", - "inner3": []interface{}{"new value 1", "new value 2"}, - "inner4": map[string]interface{}{"aa": "1", "bb": 2, "cc": []interface{}{1, 2, 3}}, - "inner5": []interface{}{map[string]interface{}{"A": "1", "B": 2, "C": []interface{}{1, 2, 3}}}, + "inner3": []any{"new value 1", "new value 2"}, + "inner4": map[string]any{"aa": "1", "bb": 2, "cc": []any{1, 2, 3}}, + "inner5": []any{map[string]any{"A": "1", "B": 2, "C": []any{1, 2, 3}}}, }, }, err: false, }, { // null assignment, and no value assigned (equivalent to null) input: "outer.inner1=,outer.inner3={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner3.cc[1]=null", - got: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": map[string]interface{}{ + got: map[string]any{ + "outer": map[string]any{ + "inner1": map[string]any{ "x": "overwrite", }, "inner2": "value2", }, }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ + expect: map[string]any{ + "outer": map[string]any{ "inner1": nil, "inner2": "value2", - "inner3": map[string]interface{}{"aa": "1", "bb": 2, "cc": []interface{}{1, nil, 3}}, + "inner3": map[string]any{"aa": "1", "bb": 2, "cc": []any{1, nil, 3}}, }, }, err: false, @@ -680,10 +680,10 @@ func TestParseJSON(t *testing.T) { func TestParseFile(t *testing.T) { input := "name1=path1" - expect := map[string]interface{}{ + expect := map[string]any{ "name1": "value1", } - rs2v := func(rs []rune) (interface{}, error) { + rs2v := func(rs []rune) (any, error) { v := string(rs) if v != "path1" { t.Errorf("%s: runesToVal: Expected value path1, got %s", input, v) @@ -712,12 +712,12 @@ func TestParseFile(t *testing.T) { } func TestParseIntoFile(t *testing.T) { - got := map[string]interface{}{} + got := map[string]any{} input := "name1=path1" - expect := map[string]interface{}{ + expect := map[string]any{ "name1": "value1", } - rs2v := func(rs []rune) (interface{}, error) { + rs2v := func(rs []rune) (any, error) { v := string(rs) if v != "path1" { t.Errorf("%s: runesToVal: Expected value path1, got %s", input, v) @@ -768,13 +768,13 @@ func TestParseSetNestedLevels(t *testing.T) { } tests := []struct { str string - expect map[string]interface{} + expect map[string]any err bool errStr string }{ { "outer.middle.inner=value", - map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}}, + map[string]any{"outer": map[string]any{"middle": map[string]any{"inner": "value"}}}, false, "", }, From 5cc2e55714d20e6d1bd2663878a00571c084d6c2 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 6 Feb 2026 16:11:01 +0100 Subject: [PATCH 11/26] chore(pkg): fix modernize linter #### Description fix modernize linter in pkg/chart/v2/lint/rules Signed-off-by: Matthieu MOREL --- pkg/action/install.go | 6 +++--- pkg/chart/common/util/values.go | 8 ++++---- pkg/chart/interfaces.go | 8 ++++---- pkg/chart/v2/lint/rules/chartfile.go | 10 +++++----- pkg/chart/v2/lint/rules/values.go | 6 +++--- pkg/cli/values/options.go | 8 ++++---- pkg/repo/v1/chartrepo_test.go | 12 ++++-------- 7 files changed, 27 insertions(+), 31 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index 0fe1f1a6e..1af46676e 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -252,7 +252,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error { // // If DryRun is set to true, this will prepare the release, but not install it -func (i *Install) Run(chrt ci.Charter, vals map[string]interface{}) (ri.Releaser, error) { +func (i *Install) Run(chrt ci.Charter, vals map[string]any) (ri.Releaser, error) { ctx := context.Background() return i.RunWithContext(ctx, chrt, vals) } @@ -261,7 +261,7 @@ func (i *Install) Run(chrt ci.Charter, vals map[string]interface{}) (ri.Releaser // // When the task is cancelled through ctx, the function returns and the install // proceeds in the background. -func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[string]interface{}) (ri.Releaser, error) { +func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[string]any) (ri.Releaser, error) { var chrt *chart.Chart switch c := ch.(type) { case *chart.Chart: @@ -638,7 +638,7 @@ func releaseV1ListToReleaserList(ls []*release.Release) ([]ri.Releaser, error) { } // createRelease creates a new release object -func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{}, labels map[string]string) *release.Release { +func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]any, labels map[string]string) *release.Release { ts := i.cfg.Now() r := &release.Release{ diff --git a/pkg/chart/common/util/values.go b/pkg/chart/common/util/values.go index 85cb29012..95ac7ba4d 100644 --- a/pkg/chart/common/util/values.go +++ b/pkg/chart/common/util/values.go @@ -26,14 +26,14 @@ import ( // ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files // // This takes both ReleaseOptions and Capabilities to merge into the render values. -func ToRenderValues(chrt chart.Charter, chrtVals map[string]interface{}, options common.ReleaseOptions, caps *common.Capabilities) (common.Values, error) { +func ToRenderValues(chrt chart.Charter, chrtVals map[string]any, options common.ReleaseOptions, caps *common.Capabilities) (common.Values, error) { return ToRenderValuesWithSchemaValidation(chrt, chrtVals, options, caps, false) } // ToRenderValuesWithSchemaValidation composes the struct from the data coming from the Releases, Charts and Values files // // This takes both ReleaseOptions and Capabilities to merge into the render values. -func ToRenderValuesWithSchemaValidation(chrt chart.Charter, chrtVals map[string]interface{}, options common.ReleaseOptions, caps *common.Capabilities, skipSchemaValidation bool) (common.Values, error) { +func ToRenderValuesWithSchemaValidation(chrt chart.Charter, chrtVals map[string]any, options common.ReleaseOptions, caps *common.Capabilities, skipSchemaValidation bool) (common.Values, error) { if caps == nil { caps = common.DefaultCapabilities } @@ -41,10 +41,10 @@ func ToRenderValuesWithSchemaValidation(chrt chart.Charter, chrtVals map[string] if err != nil { return nil, err } - top := map[string]interface{}{ + top := map[string]any{ "Chart": accessor.MetadataAsMap(), "Capabilities": caps, - "Release": map[string]interface{}{ + "Release": map[string]any{ "Name": options.Name, "Namespace": options.Namespace, "IsUpgrade": options.IsUpgrade, diff --git a/pkg/chart/interfaces.go b/pkg/chart/interfaces.go index 4001bc548..6d94ad3ea 100644 --- a/pkg/chart/interfaces.go +++ b/pkg/chart/interfaces.go @@ -19,21 +19,21 @@ import ( common "helm.sh/helm/v4/pkg/chart/common" ) -type Charter interface{} +type Charter any -type Dependency interface{} +type Dependency any type Accessor interface { Name() string IsRoot() bool - MetadataAsMap() map[string]interface{} + MetadataAsMap() map[string]any Files() []*common.File Templates() []*common.File ChartFullPath() string IsLibraryChart() bool Dependencies() []Charter MetaDependencies() []Dependency - Values() map[string]interface{} + Values() map[string]any Schema() []byte Deprecated() bool } diff --git a/pkg/chart/v2/lint/rules/chartfile.go b/pkg/chart/v2/lint/rules/chartfile.go index 806363477..b2d146a27 100644 --- a/pkg/chart/v2/lint/rules/chartfile.go +++ b/pkg/chart/v2/lint/rules/chartfile.go @@ -70,15 +70,15 @@ func Chartfile(linter *support.Linter) { linter.RunLinterRule(support.WarningSev, chartFileName, validateChartVersionStrictSemVerV2(chartFile)) } -func validateChartVersionType(data map[string]interface{}) error { +func validateChartVersionType(data map[string]any) error { return isStringValue(data, "version") } -func validateChartAppVersionType(data map[string]interface{}) error { +func validateChartAppVersionType(data map[string]any) error { return isStringValue(data, "appVersion") } -func isStringValue(data map[string]interface{}, key string) error { +func isStringValue(data map[string]any, key string) error { value, ok := data[key] if !ok { return nil @@ -225,12 +225,12 @@ func validateChartType(cf *chart.Metadata) error { // loadChartFileForTypeCheck loads the Chart.yaml // in a generic form of a map[string]interface{}, so that the type // of the values can be checked -func loadChartFileForTypeCheck(filename string) (map[string]interface{}, error) { +func loadChartFileForTypeCheck(filename string) (map[string]any, error) { b, err := os.ReadFile(filename) if err != nil { return nil, err } - y := make(map[string]interface{}) + y := make(map[string]any) err = yaml.Unmarshal(b, &y) return y, err } diff --git a/pkg/chart/v2/lint/rules/values.go b/pkg/chart/v2/lint/rules/values.go index 994a6a463..8fe849c7a 100644 --- a/pkg/chart/v2/lint/rules/values.go +++ b/pkg/chart/v2/lint/rules/values.go @@ -32,7 +32,7 @@ import ( // they are only tested for well-formedness. // // If additional values are supplied, they are coalesced into the values in values.yaml. -func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]interface{}, skipSchemaValidation bool) { +func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]any, skipSchemaValidation bool) { file := "values.yaml" vf := filepath.Join(linter.ChartDir, file) fileExists := linter.RunLinterRule(support.InfoSev, file, validateValuesFileExistence(vf)) @@ -52,7 +52,7 @@ func validateValuesFileExistence(valuesPath string) error { return nil } -func validateValuesFile(valuesPath string, overrides map[string]interface{}, skipSchemaValidation bool) error { +func validateValuesFile(valuesPath string, overrides map[string]any, skipSchemaValidation bool) error { values, err := common.ReadValuesFile(valuesPath) if err != nil { return fmt.Errorf("unable to parse YAML: %w", err) @@ -63,7 +63,7 @@ func validateValuesFile(valuesPath string, overrides map[string]interface{}, ski // We could change that. For now, though, we retain that strategy, and thus can // coalesce tables (like reuse-values does) instead of doing the full chart // CoalesceValues - coalescedValues := util.CoalesceTables(make(map[string]interface{}, len(overrides)), overrides) + coalescedValues := util.CoalesceTables(make(map[string]any, len(overrides)), overrides) coalescedValues = util.CoalesceTables(coalescedValues, values) ext := filepath.Ext(valuesPath) diff --git a/pkg/cli/values/options.go b/pkg/cli/values/options.go index cd65fa885..336dfb72b 100644 --- a/pkg/cli/values/options.go +++ b/pkg/cli/values/options.go @@ -42,8 +42,8 @@ type Options struct { // MergeValues merges values from files specified via -f/--values and directly // via --set-json, --set, --set-string, or --set-file, marshaling them to YAML -func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, error) { - base := map[string]interface{}{} +func (opts *Options) MergeValues(p getter.Providers) (map[string]any, error) { + base := map[string]any{} // User specified a values files via -f/--values for _, filePath := range opts.ValueFiles { @@ -64,7 +64,7 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er trimmedValue := strings.TrimSpace(value) if len(trimmedValue) > 0 && trimmedValue[0] == '{' { // If value is JSON object format, parse it as map - var jsonMap map[string]interface{} + var jsonMap map[string]any if err := json.Unmarshal([]byte(trimmedValue), &jsonMap); err != nil { return nil, fmt.Errorf("failed parsing --set-json data JSON: %s", value) } @@ -93,7 +93,7 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er // User specified a value via --set-file for _, value := range opts.FileValues { - reader := func(rs []rune) (interface{}, error) { + reader := func(rs []rune) (any, error) { bytes, err := readFile(string(rs), p) if err != nil { return nil, err diff --git a/pkg/repo/v1/chartrepo_test.go b/pkg/repo/v1/chartrepo_test.go index 353ab62d6..7cffc04b6 100644 --- a/pkg/repo/v1/chartrepo_test.go +++ b/pkg/repo/v1/chartrepo_test.go @@ -126,24 +126,20 @@ func TestConcurrencyDownloadIndex(t *testing.T) { // 2) read index.yaml via LoadIndexFile (read operation). // This checks for race conditions and ensures correct behavior under concurrent read/write access. for range 150 { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { idx, err := repo.DownloadIndexFile() if err != nil { t.Errorf("Failed to download index file to %s: %v", idx, err) } - }() + }) - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { _, err := LoadIndexFile(indexFName) if err != nil { t.Errorf("Failed to load index file: %v", err) } - }() + }) } wg.Wait() } From 859292e31bd4ceb170050eaa49e727bcd69572e2 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 6 Feb 2026 16:11:18 +0100 Subject: [PATCH 12/26] chore(internal): fix modernize linter #### Description fix modernize linter in internal/chart/v3 Signed-off-by: Matthieu MOREL --- internal/chart/v3/chart.go | 2 +- internal/chart/v3/dependency.go | 2 +- internal/chart/v3/errors.go | 2 +- internal/chart/v3/lint/lint.go | 2 +- internal/chart/v3/lint/rules/template_test.go | 4 ++-- internal/chart/v3/util/create.go | 2 +- internal/test/test.go | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/chart/v3/chart.go b/internal/chart/v3/chart.go index 48f006e79..217594df9 100644 --- a/internal/chart/v3/chart.go +++ b/internal/chart/v3/chart.go @@ -45,7 +45,7 @@ type Chart struct { // Templates for this chart. Templates []*common.File `json:"templates"` // Values are default config for this chart. - Values map[string]interface{} `json:"values"` + Values map[string]any `json:"values"` // Schema is an optional JSON schema for imposing structure on Values Schema []byte `json:"schema"` // SchemaModTime the schema was last modified diff --git a/internal/chart/v3/dependency.go b/internal/chart/v3/dependency.go index 2d956b548..50ee5552e 100644 --- a/internal/chart/v3/dependency.go +++ b/internal/chart/v3/dependency.go @@ -44,7 +44,7 @@ type Dependency struct { Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` // ImportValues holds the mapping of source values to parent key to be imported. Each item can be a // string or pair of child/parent sublist items. - ImportValues []interface{} `json:"import-values,omitempty" yaml:"import-values,omitempty"` + ImportValues []any `json:"import-values,omitempty" yaml:"import-values,omitempty"` // Alias usable alias to be used for the chart Alias string `json:"alias,omitempty" yaml:"alias,omitempty"` } diff --git a/internal/chart/v3/errors.go b/internal/chart/v3/errors.go index 059e43f07..156dce1de 100644 --- a/internal/chart/v3/errors.go +++ b/internal/chart/v3/errors.go @@ -25,6 +25,6 @@ func (v ValidationError) Error() string { } // ValidationErrorf takes a message and formatting options and creates a ValidationError -func ValidationErrorf(msg string, args ...interface{}) ValidationError { +func ValidationErrorf(msg string, args ...any) ValidationError { return ValidationError(fmt.Sprintf(msg, args...)) } diff --git a/internal/chart/v3/lint/lint.go b/internal/chart/v3/lint/lint.go index 0cd949065..e98edfabe 100644 --- a/internal/chart/v3/lint/lint.go +++ b/internal/chart/v3/lint/lint.go @@ -43,7 +43,7 @@ func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption { } } -func RunAll(baseDir string, values map[string]interface{}, namespace string, options ...LinterOption) support.Linter { +func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter { chartDir, _ := filepath.Abs(baseDir) diff --git a/internal/chart/v3/lint/rules/template_test.go b/internal/chart/v3/lint/rules/template_test.go index 0ffc92002..b1371659f 100644 --- a/internal/chart/v3/lint/rules/template_test.go +++ b/internal/chart/v3/lint/rules/template_test.go @@ -49,7 +49,7 @@ func TestValidateAllowedExtension(t *testing.T) { } } -var values = map[string]interface{}{"nameOverride": "", "httpPort": 80} +var values = map[string]any{"nameOverride": "", "httpPort": 80} const namespace = "testNamespace" const strict = false @@ -249,7 +249,7 @@ func TestStrictTemplateParsingMapError(t *testing.T) { APIVersion: "v2", Version: "0.1.0", }, - Values: map[string]interface{}{ + Values: map[string]any{ "mymap": map[string]string{ "key1": "val1", }, diff --git a/internal/chart/v3/util/create.go b/internal/chart/v3/util/create.go index 0dfa30995..48d2120e5 100644 --- a/internal/chart/v3/util/create.go +++ b/internal/chart/v3/util/create.go @@ -670,7 +670,7 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error { return fmt.Errorf("reading values file: %w", err) } - var m map[string]interface{} + var m map[string]any if err := yaml.Unmarshal(transform(string(b), schart.Name()), &m); err != nil { return fmt.Errorf("transforming values file: %w", err) } diff --git a/internal/test/test.go b/internal/test/test.go index 632bc72fd..202e015ab 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -29,8 +29,8 @@ var updateGolden = flag.Bool("update", false, "update golden files") // TestingT describes a testing object compatible with the critical functions from the testing.T type type TestingT interface { - Fatal(...interface{}) - Fatalf(string, ...interface{}) + Fatal(...any) + Fatalf(string, ...any) HelperT } From 5aac32077f87ed8cd80da1648abbd323320d4a0b Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Fri, 6 Feb 2026 18:29:59 +0000 Subject: [PATCH 13/26] feat(create): add --chart-api-version flag (when HELM_EXPERIMENTAL_CHART_V3 env var is set) (#31592) * feat(create): add hidden --chart-api-version flag Add --chart-api-version flag to helm create command to allow selecting chart API version (v2 or v3) when creating a new chart. - Default is v2 (existing behavior unchanged) - v3 uses internal/chart/v3 scaffold generator - Invalid versions return clear error message - Works with --starter flag Signed-off-by: Evans Mungai * Add HELM_EXPERIMENTAL_CHART_V3 feature gate to create command Signed-off-by: Evans Mungai * make chartv3 private and use loader to load the chart Signed-off-by: Evans Mungai * Hide chart-api-version flag until chart v3 is officially released Signed-off-by: Evans Mungai * Conditionally hide the --chart-api-version flag if chart v3 is not enabled Signed-off-by: Evans Mungai * Add internal gates package for internal feature gates Signed-off-by: Evans Mungai * Add doc for internal/gates package Signed-off-by: Evans Mungai --------- Signed-off-by: Evans Mungai --- internal/gates/doc.go | 19 +++ internal/gates/gates.go | 21 +++ pkg/cmd/create.go | 56 ++++++- pkg/cmd/create_test.go | 360 ++++++++++++++++++++++++++-------------- 4 files changed, 330 insertions(+), 126 deletions(-) create mode 100644 internal/gates/doc.go create mode 100644 internal/gates/gates.go diff --git a/internal/gates/doc.go b/internal/gates/doc.go new file mode 100644 index 000000000..09501e005 --- /dev/null +++ b/internal/gates/doc.go @@ -0,0 +1,19 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package gates contains internal feature gates that can be used to enable or disable experimental features. +// This is a separate internal package instead of using the pkg/gates package to avoid circular dependencies. +package gates diff --git a/internal/gates/gates.go b/internal/gates/gates.go new file mode 100644 index 000000000..e071c54ea --- /dev/null +++ b/internal/gates/gates.go @@ -0,0 +1,21 @@ +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gates + +import "helm.sh/helm/v4/pkg/gates" + +// ChartV3 is the feature gate for chart API version v3. +const ChartV3 gates.Gate = "HELM_EXPERIMENTAL_CHART_V3" diff --git a/pkg/cmd/create.go b/pkg/cmd/create.go index 435c8ca82..b38ce6a12 100644 --- a/pkg/cmd/create.go +++ b/pkg/cmd/create.go @@ -23,6 +23,9 @@ import ( "github.com/spf13/cobra" + chartv3 "helm.sh/helm/v4/internal/chart/v3" + chartutilv3 "helm.sh/helm/v4/internal/chart/v3/util" + "helm.sh/helm/v4/internal/gates" chart "helm.sh/helm/v4/pkg/chart/v2" chartutil "helm.sh/helm/v4/pkg/chart/v2/util" "helm.sh/helm/v4/pkg/cmd/require" @@ -51,9 +54,10 @@ will be overwritten, but other files will be left alone. ` type createOptions struct { - starter string // --starter - name string - starterDir string + starter string // --starter + name string + starterDir string + chartAPIVersion string // --chart-api-version } func newCreateCmd(out io.Writer) *cobra.Command { @@ -81,12 +85,32 @@ func newCreateCmd(out io.Writer) *cobra.Command { } cmd.Flags().StringVarP(&o.starter, "starter", "p", "", "the name or absolute path to Helm starter scaffold") + cmd.Flags().StringVar(&o.chartAPIVersion, "chart-api-version", chart.APIVersionV2, "chart API version to use (v2 or v3)") + + if !gates.ChartV3.IsEnabled() { + cmd.Flags().MarkHidden("chart-api-version") + } + return cmd } func (o *createOptions) run(out io.Writer) error { fmt.Fprintf(out, "Creating %s\n", o.name) + switch o.chartAPIVersion { + case chart.APIVersionV2, "": + return o.createV2Chart(out) + case chartv3.APIVersionV3: + if !gates.ChartV3.IsEnabled() { + return gates.ChartV3.Error() + } + return o.createV3Chart(out) + default: + return fmt.Errorf("unsupported chart API version: %s (supported: v2, v3)", o.chartAPIVersion) + } +} + +func (o *createOptions) createV2Chart(out io.Writer) error { chartname := filepath.Base(o.name) cfile := &chart.Metadata{ Name: chartname, @@ -111,3 +135,29 @@ func (o *createOptions) run(out io.Writer) error { _, err := chartutil.Create(chartname, filepath.Dir(o.name)) return err } + +func (o *createOptions) createV3Chart(out io.Writer) error { + chartname := filepath.Base(o.name) + cfile := &chartv3.Metadata{ + Name: chartname, + Description: "A Helm chart for Kubernetes", + Type: "application", + Version: "0.1.0", + AppVersion: "0.1.0", + APIVersion: chartv3.APIVersionV3, + } + + if o.starter != "" { + // Create from the starter + lstarter := filepath.Join(o.starterDir, o.starter) + // If path is absolute, we don't want to prefix it with helm starters folder + if filepath.IsAbs(o.starter) { + lstarter = o.starter + } + return chartutilv3.CreateFrom(cfile, filepath.Dir(o.name), lstarter) + } + + chartutilv3.Stderr = out + _, err := chartutilv3.Create(chartname, filepath.Dir(o.name)) + return err +} diff --git a/pkg/cmd/create_test.go b/pkg/cmd/create_test.go index 90ed90eff..57e2aaf5f 100644 --- a/pkg/cmd/create_test.go +++ b/pkg/cmd/create_test.go @@ -22,9 +22,13 @@ import ( "path/filepath" "testing" + chartv3 "helm.sh/helm/v4/internal/chart/v3" + chartutilv3 "helm.sh/helm/v4/internal/chart/v3/util" + "helm.sh/helm/v4/internal/gates" "helm.sh/helm/v4/internal/test/ensure" - chart "helm.sh/helm/v4/pkg/chart/v2" - "helm.sh/helm/v4/pkg/chart/v2/loader" + chart "helm.sh/helm/v4/pkg/chart" + chartloader "helm.sh/helm/v4/pkg/chart/loader" + chartv2 "helm.sh/helm/v4/pkg/chart/v2" chartutil "helm.sh/helm/v4/pkg/chart/v2/util" "helm.sh/helm/v4/pkg/helmpath" ) @@ -46,143 +50,155 @@ func TestCreateCmd(t *testing.T) { t.Fatalf("chart is not directory") } - c, err := loader.LoadDir(cname) + c, err := chartloader.LoadDir(cname) if err != nil { t.Fatal(err) } - if c.Name() != cname { - t.Errorf("Expected %q name, got %q", cname, c.Name()) + acc, err := chart.NewAccessor(c) + if err != nil { + t.Fatal(err) } - if c.Metadata.APIVersion != chart.APIVersionV2 { - t.Errorf("Wrong API version: %q", c.Metadata.APIVersion) + + if acc.Name() != cname { + t.Errorf("Expected %q name, got %q", cname, acc.Name()) + } + metadata := acc.MetadataAsMap() + apiVersion, ok := metadata["APIVersion"].(string) + if !ok { + t.Fatal("APIVersion not found in metadata") + } + if apiVersion != chartv2.APIVersionV2 { + t.Errorf("Wrong API version: %q", apiVersion) } } func TestCreateStarterCmd(t *testing.T) { - t.Chdir(t.TempDir()) - ensure.HelmHome(t) - cname := "testchart" - defer resetEnv()() - // Create a starter. - starterchart := helmpath.DataPath("starters") - os.MkdirAll(starterchart, 0o755) - if dest, err := chartutil.Create("starterchart", starterchart); err != nil { - t.Fatalf("Could not create chart: %s", err) - } else { - t.Logf("Created %s", dest) - } - tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") - if err := os.WriteFile(tplpath, []byte("test"), 0o644); err != nil { - t.Fatalf("Could not write template: %s", err) + tests := []struct { + name string + chartAPIVersion string + useAbsolutePath bool + expectedVersion string + }{ + { + name: "v2 with relative starter path", + chartAPIVersion: "", + useAbsolutePath: false, + expectedVersion: chartv2.APIVersionV2, + }, + { + name: "v2 with absolute starter path", + chartAPIVersion: "", + useAbsolutePath: true, + expectedVersion: chartv2.APIVersionV2, + }, + { + name: "v3 with relative starter path", + chartAPIVersion: "v3", + useAbsolutePath: false, + expectedVersion: chartv3.APIVersionV3, + }, } - // Run a create - if _, _, err := executeActionCommand(fmt.Sprintf("create --starter=starterchart %s", cname)); err != nil { - t.Errorf("Failed to run create: %s", err) - return - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Chdir(t.TempDir()) + ensure.HelmHome(t) + defer resetEnv()() - // Test that the chart is there - if fi, err := os.Stat(cname); err != nil { - t.Fatalf("no chart directory: %s", err) - } else if !fi.IsDir() { - t.Fatalf("chart is not directory") - } - - c, err := loader.LoadDir(cname) - if err != nil { - t.Fatal(err) - } - - if c.Name() != cname { - t.Errorf("Expected %q name, got %q", cname, c.Name()) - } - if c.Metadata.APIVersion != chart.APIVersionV2 { - t.Errorf("Wrong API version: %q", c.Metadata.APIVersion) - } - - expectedNumberOfTemplates := 10 - if l := len(c.Templates); l != expectedNumberOfTemplates { - t.Errorf("Expected %d templates, got %d", expectedNumberOfTemplates, l) - } - - found := false - for _, tpl := range c.Templates { - if tpl.Name == "templates/foo.tpl" { - found = true - if data := string(tpl.Data); data != "test" { - t.Errorf("Expected template 'test', got %q", data) + // Enable feature gate for v3 charts + if tt.chartAPIVersion == "v3" { + t.Setenv(string(gates.ChartV3), "1") } - } - } - if !found { - t.Error("Did not find foo.tpl") - } -} -func TestCreateStarterAbsoluteCmd(t *testing.T) { - t.Chdir(t.TempDir()) - defer resetEnv()() - ensure.HelmHome(t) - cname := "testchart" + cname := "testchart" - // Create a starter. - starterchart := helmpath.DataPath("starters") - os.MkdirAll(starterchart, 0o755) - if dest, err := chartutil.Create("starterchart", starterchart); err != nil { - t.Fatalf("Could not create chart: %s", err) - } else { - t.Logf("Created %s", dest) - } - tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") - if err := os.WriteFile(tplpath, []byte("test"), 0o644); err != nil { - t.Fatalf("Could not write template: %s", err) - } - - starterChartPath := filepath.Join(starterchart, "starterchart") - - // Run a create - if _, _, err := executeActionCommand(fmt.Sprintf("create --starter=%s %s", starterChartPath, cname)); err != nil { - t.Errorf("Failed to run create: %s", err) - return - } - - // Test that the chart is there - if fi, err := os.Stat(cname); err != nil { - t.Fatalf("no chart directory: %s", err) - } else if !fi.IsDir() { - t.Fatalf("chart is not directory") - } - - c, err := loader.LoadDir(cname) - if err != nil { - t.Fatal(err) - } - - if c.Name() != cname { - t.Errorf("Expected %q name, got %q", cname, c.Name()) - } - if c.Metadata.APIVersion != chart.APIVersionV2 { - t.Errorf("Wrong API version: %q", c.Metadata.APIVersion) - } - - expectedNumberOfTemplates := 10 - if l := len(c.Templates); l != expectedNumberOfTemplates { - t.Errorf("Expected %d templates, got %d", expectedNumberOfTemplates, l) - } - - found := false - for _, tpl := range c.Templates { - if tpl.Name == "templates/foo.tpl" { - found = true - if data := string(tpl.Data); data != "test" { - t.Errorf("Expected template 'test', got %q", data) + // Create a starter using the appropriate chartutil + starterchart := helmpath.DataPath("starters") + os.MkdirAll(starterchart, 0o755) + var err error + var dest string + if tt.chartAPIVersion == "v3" { + dest, err = chartutilv3.Create("starterchart", starterchart) + } else { + dest, err = chartutil.Create("starterchart", starterchart) } - } - } - if !found { - t.Error("Did not find foo.tpl") + if err != nil { + t.Fatalf("Could not create chart: %s", err) + } + t.Logf("Created %s", dest) + + tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") + if err := os.WriteFile(tplpath, []byte("test"), 0o644); err != nil { + t.Fatalf("Could not write template: %s", err) + } + + // Build the command + starterArg := "starterchart" + if tt.useAbsolutePath { + starterArg = filepath.Join(starterchart, "starterchart") + } + cmd := fmt.Sprintf("create --starter=%s", starterArg) + if tt.chartAPIVersion == "v3" { + cmd += fmt.Sprintf(" --chart-api-version=%s", chartv3.APIVersionV3) + } else { + cmd += fmt.Sprintf(" --chart-api-version=%s", chartv2.APIVersionV2) + } + cmd += " " + cname + + // Run create + if _, _, err := executeActionCommand(cmd); err != nil { + t.Fatalf("Failed to run create: %s", err) + } + + // Test that the chart is there + if fi, err := os.Stat(cname); err != nil { + t.Fatalf("no chart directory: %s", err) + } else if !fi.IsDir() { + t.Fatalf("chart is not directory") + } + + // Load and verify the chart + c, err := chartloader.LoadDir(cname) + if err != nil { + t.Fatal(err) + } + + acc, err := chart.NewAccessor(c) + if err != nil { + t.Fatal(err) + } + + chartName := acc.Name() + metadata := acc.MetadataAsMap() + apiVersion, ok := metadata["APIVersion"].(string) + if !ok { + t.Fatal("APIVersion not found in metadata") + } + var templates []string + for _, tpl := range acc.Templates() { + templates = append(templates, tpl.Name) + } + + if chartName != cname { + t.Errorf("Expected %q name, got %q", cname, chartName) + } + if apiVersion != tt.expectedVersion { + t.Errorf("Wrong API version: expected %q, got %q", tt.expectedVersion, apiVersion) + } + + // Verify custom template exists + found := false + for _, name := range templates { + if name == "templates/foo.tpl" { + found = true + break + } + } + if !found { + t.Error("Did not find foo.tpl") + } + }) } } @@ -190,3 +206,101 @@ func TestCreateFileCompletion(t *testing.T) { checkFileCompletion(t, "create", true) checkFileCompletion(t, "create myname", false) } + +func TestCreateCmdChartAPIVersionV2(t *testing.T) { + t.Chdir(t.TempDir()) + ensure.HelmHome(t) + cname := "testchart" + + // Run a create with explicit v2 + if _, _, err := executeActionCommand("create --chart-api-version=v2 " + cname); err != nil { + t.Fatalf("Failed to run create: %s", err) + } + + // Test that the chart is there + if fi, err := os.Stat(cname); err != nil { + t.Fatalf("no chart directory: %s", err) + } else if !fi.IsDir() { + t.Fatalf("chart is not directory") + } + + c, err := chartloader.LoadDir(cname) + if err != nil { + t.Fatal(err) + } + + acc, err := chart.NewAccessor(c) + if err != nil { + t.Fatal(err) + } + + if acc.Name() != cname { + t.Errorf("Expected %q name, got %q", cname, acc.Name()) + } + metadata := acc.MetadataAsMap() + apiVersion, ok := metadata["APIVersion"].(string) + if !ok { + t.Fatal("APIVersion not found in metadata") + } + if apiVersion != chartv2.APIVersionV2 { + t.Errorf("Wrong API version: expected %q, got %q", chartv2.APIVersionV2, apiVersion) + } +} + +func TestCreateCmdChartAPIVersionV3(t *testing.T) { + t.Chdir(t.TempDir()) + ensure.HelmHome(t) + t.Setenv(string(gates.ChartV3), "1") + cname := "testchart" + + // Run a create with v3 + if _, _, err := executeActionCommand("create --chart-api-version=v3 " + cname); err != nil { + t.Fatalf("Failed to run create: %s", err) + } + + // Test that the chart is there + if fi, err := os.Stat(cname); err != nil { + t.Fatalf("no chart directory: %s", err) + } else if !fi.IsDir() { + t.Fatalf("chart is not directory") + } + + c, err := chartloader.LoadDir(cname) + if err != nil { + t.Fatal(err) + } + + acc, err := chart.NewAccessor(c) + if err != nil { + t.Fatal(err) + } + + if acc.Name() != cname { + t.Errorf("Expected %q name, got %q", cname, acc.Name()) + } + metadata := acc.MetadataAsMap() + apiVersion, ok := metadata["APIVersion"].(string) + if !ok { + t.Fatal("APIVersion not found in metadata") + } + if apiVersion != chartv3.APIVersionV3 { + t.Errorf("Wrong API version: expected %q, got %q", chartv3.APIVersionV3, apiVersion) + } +} + +func TestCreateCmdInvalidChartAPIVersion(t *testing.T) { + t.Chdir(t.TempDir()) + ensure.HelmHome(t) + cname := "testchart" + + // Run a create with invalid version + _, _, err := executeActionCommand("create --chart-api-version=v1 " + cname) + if err == nil { + t.Fatal("Expected error for invalid API version, got nil") + } + + expectedErr := "unsupported chart API version: v1 (supported: v2, v3)" + if err.Error() != expectedErr { + t.Errorf("Expected error %q, got %q", expectedErr, err.Error()) + } +} From 9817a68618245370e98e09d7f06c7cc1cefe8a62 Mon Sep 17 00:00:00 2001 From: Manuel Alonso <434575+manute@users.noreply.github.com> Date: Sat, 7 Feb 2026 11:27:24 +0100 Subject: [PATCH 14/26] fix(install): check nil for restClientGetter and fix tests Signed-off-by: Manuel Alonso <434575+manute@users.noreply.github.com> --- pkg/action/install.go | 39 +++++++++-------- pkg/action/install_test.go | 89 ++++++++++++++++++++++++++++++++------ 2 files changed, 96 insertions(+), 32 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index b019771e1..c37f3e4d1 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -234,27 +234,30 @@ func (i *Install) installCRDs(crds []chart.CRD) error { // the case when an action configuration is reused for multiple actions, // as otherwise it is later loaded by ourselves when getCapabilities // is called later on in the installation process. - if i.cfg.Capabilities != nil { - discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() + if i.cfg.RESTClientGetter != nil { + if i.cfg.Capabilities != nil { + discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() + if err != nil { + return err + } + + if discoveryClient != nil { + i.cfg.Logger().Debug("clearing discovery cache") + discoveryClient.Invalidate() + _, _ = discoveryClient.ServerGroups() + } + } + + // Invalidate the REST mapper, since it will not have the new CRDs + // present. + restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper() if err != nil { return err } - - i.cfg.Logger().Debug("clearing discovery cache") - discoveryClient.Invalidate() - - _, _ = discoveryClient.ServerGroups() - } - - // Invalidate the REST mapper, since it will not have the new CRDs - // present. - restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper() - if err != nil { - return err - } - if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { - i.cfg.Logger().Debug("clearing REST mapper cache") - resettable.Reset() + if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { + i.cfg.Logger().Debug("clearing REST mapper cache") + resettable.Reset() + } } } return nil diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 4b9b19d5d..52dd83788 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -35,6 +35,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kuberuntime "k8s.io/apimachinery/pkg/runtime" @@ -44,7 +45,6 @@ import ( "k8s.io/client-go/rest/fake" ci "helm.sh/helm/v4/pkg/chart" - "helm.sh/helm/v4/pkg/cli" "helm.sh/helm/v4/internal/test" "helm.sh/helm/v4/pkg/chart/common" @@ -111,6 +111,54 @@ func createDummyResourceList(owned bool) kube.ResourceList { return resourceList } +func createDummyCRDList(owned bool) kube.ResourceList { + obj := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummyName", + Namespace: "spaced", + }, + } + + if owned { + obj.Labels = map[string]string{ + "app.kubernetes.io/managed-by": "Helm", + } + obj.Annotations = map[string]string{ + "meta.helm.sh/release-name": "test-install-release", + "meta.helm.sh/release-namespace": "spaced", + } + } + + resInfo := resource.Info{ + Name: "dummyName", + Namespace: "spaced", + Mapping: &meta.RESTMapping{ + Resource: schema.GroupVersionResource{Group: "test", Version: "v1", Resource: "crd"}, + GroupVersionKind: schema.GroupVersionKind{Group: "test", Version: "v1", Kind: "crd"}, + Scope: meta.RESTScopeNamespace, + }, + Object: obj, + } + body := io.NopCloser(bytes.NewReader([]byte(kuberuntime.EncodeOrDie(appsv1Codec, obj)))) + + resInfo.Client = &fake.RESTClient{ + GroupVersion: schema.GroupVersion{Group: "test", Version: "v1"}, + NegotiatedSerializer: scheme.Codecs.WithoutConversion(), + Client: fake.CreateHTTPClient(func(_ *http.Request) (*http.Response, error) { + header := http.Header{} + header.Set("Content-Type", kuberuntime.ContentTypeJSON) + return &http.Response{ + StatusCode: http.StatusOK, + Header: header, + Body: body, + }, nil + }), + } + var resourceList kube.ResourceList + resourceList.Append(&resInfo) + return resourceList +} + func installActionWithConfig(config *Configuration) *Install { instAction := NewInstall(config) instAction.Namespace = "spaced" @@ -1085,9 +1133,7 @@ func TestInstallSetRegistryClient(t *testing.T) { } func TestInstallCRDs(t *testing.T) { - config := actionConfigFixtureWithDummyResources(t, createDummyResourceList(false)) - config.RESTClientGetter = cli.New().RESTClientGetter() - + config := actionConfigFixtureWithDummyResources(t, createDummyCRDList(false)) instAction := NewInstall(config) mockFile := common.File{ @@ -1097,17 +1143,32 @@ func TestInstallCRDs(t *testing.T) { mockChart := buildChart(withFile(mockFile)) crdsToInstall := mockChart.CRDObjects() - t.Run("fresh installation", func(t *testing.T) { - assert.Len(t, crdsToInstall, 1) - assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) - require.NoError(t, instAction.installCRDs(crdsToInstall)) - }) + assert.Len(t, crdsToInstall, 1) + assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) + require.NoError(t, instAction.installCRDs(crdsToInstall)) +} - t.Run("already exist", func(t *testing.T) { - assert.Len(t, crdsToInstall, 1) - assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data) - require.NoError(t, instAction.installCRDs(crdsToInstall)) - }) +func TestInstallCRDs_AlreadyExist(t *testing.T) { + dummyResources := createDummyCRDList(false) + failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: dummyResources} + mockError := &apierrors.StatusError{ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Reason: metav1.StatusReasonAlreadyExists, + }} + failingKubeClient.CreateError = mockError + + config := actionConfigFixtureWithDummyResources(t, dummyResources) + config.KubeClient = &failingKubeClient + instAction := NewInstall(config) + + mockFile := common.File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + } + mockChart := buildChart(withFile(mockFile)) + crdsToInstall := mockChart.CRDObjects() + + assert.Nil(t, instAction.installCRDs(crdsToInstall)) } func TestInstallCRDs_KubeClient_BuildError(t *testing.T) { From 76eb37c01aaece271343039f44d7803017dd5c81 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Mon, 11 Aug 2025 09:08:43 +0300 Subject: [PATCH 15/26] fix(downloader): safely handle concurrent file writes on Windows When multiple processes try to download the same chart version concurrently (e.g., via Terraform), they can race to write the destination file. On Windows, this results in 'Access Denied' errors because the file cannot be renamed while another process has a handle to the destination. This commit introduces 'PlatformAtomicWriteFile' to the fileutil package. On Unix-like systems, it simply delegates to AtomicWriteFile, maintaining existing behavior. On Windows, it coordinates writes using a lock file (.lock). It acquires the lock, then performs the atomic write. Crucially, this implementation ensures that existing files are overwritten (rather than skipped). This ensures that if a chart is republished with the same version, the local cache is correctly updated, preventing stale data issues. Fixes #31633 Signed-off-by: Orgad Shaneh --- internal/fileutil/fileutil_test.go | 26 ++++ internal/fileutil/fileutil_unix.go | 32 +++++ internal/fileutil/fileutil_windows.go | 54 ++++++++ pkg/downloader/chart_downloader.go | 10 +- .../chart_downloader_windows_test.go | 131 ++++++++++++++++++ 5 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 internal/fileutil/fileutil_unix.go create mode 100644 internal/fileutil/fileutil_windows.go create mode 100644 pkg/downloader/chart_downloader_windows_test.go diff --git a/internal/fileutil/fileutil_test.go b/internal/fileutil/fileutil_test.go index 881fbb49d..71fcae177 100644 --- a/internal/fileutil/fileutil_test.go +++ b/internal/fileutil/fileutil_test.go @@ -119,3 +119,29 @@ func TestAtomicWriteFile_LargeContent(t *testing.T) { t.Fatalf("expected large content to match, got different length: %d vs %d", len(largeContent), len(got)) } } + +// TestPlatformAtomicWriteFile_OverwritesExisting verifies that the platform +// helper replaces existing files instead of silently skipping them. +func TestPlatformAtomicWriteFile_OverwritesExisting(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "overwrite_test") + + first := bytes.NewReader([]byte("first")) + if err := PlatformAtomicWriteFile(path, first, 0644); err != nil { + t.Fatalf("first write failed: %v", err) + } + + second := bytes.NewReader([]byte("second")) + if err := PlatformAtomicWriteFile(path, second, 0644); err != nil { + t.Fatalf("second write failed: %v", err) + } + + contents, err := os.ReadFile(path) + if err != nil { + t.Fatalf("failed reading result: %v", err) + } + + if string(contents) != "second" { + t.Fatalf("expected file to be overwritten, got %q", string(contents)) + } +} diff --git a/internal/fileutil/fileutil_unix.go b/internal/fileutil/fileutil_unix.go new file mode 100644 index 000000000..bbacb10bf --- /dev/null +++ b/internal/fileutil/fileutil_unix.go @@ -0,0 +1,32 @@ +//go:build !windows + +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fileutil + +import ( + "io" + "os" +) + +// PlatformAtomicWriteFile atomically writes a file to disk. +// +// On non-Windows platforms we don't need extra coordination, so this simply +// delegates to AtomicWriteFile to preserve the existing overwrite behaviour. +func PlatformAtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error { + return AtomicWriteFile(filename, reader, mode) +} diff --git a/internal/fileutil/fileutil_windows.go b/internal/fileutil/fileutil_windows.go new file mode 100644 index 000000000..179237860 --- /dev/null +++ b/internal/fileutil/fileutil_windows.go @@ -0,0 +1,54 @@ +//go:build windows + +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fileutil + +import ( + "io" + "os" + + "github.com/gofrs/flock" +) + +// PlatformAtomicWriteFile atomically writes a file to disk with file locking to +// prevent concurrent writes. This is particularly useful on Windows where +// concurrent writes to the same file can cause "Access Denied" errors. +// +// The function acquires a lock on the target file and performs an atomic write, +// preserving the existing behaviour of overwriting any previous content once +// the lock is obtained. +func PlatformAtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error { + // Use a separate lock file to coordinate access between processes + // We cannot lock the target file directly as it would prevent the atomic rename + lockFileName := filename + ".lock" + fileLock := flock.New(lockFileName) + + // Lock() ensures serialized access - if another process is writing, this will wait + if err := fileLock.Lock(); err != nil { + return err + } + defer func() { + fileLock.Unlock() + // Clean up the lock file + // Ignore errors as the file might not exist or be in use by another process + os.Remove(lockFileName) + }() + + // Perform the atomic write while holding the lock + return AtomicWriteFile(filename, reader, mode) +} diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index ee4f8abe3..ea178dfe9 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -156,7 +156,11 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven } destfile := filepath.Join(dest, name) - if err := fileutil.AtomicWriteFile(destfile, data, 0644); err != nil { + + // Use PlatformAtomicWriteFile to handle platform-specific concurrency concerns + // (Windows requires locking to avoid "Access Denied" errors when multiple + // processes write the same file) + if err := fileutil.PlatformAtomicWriteFile(destfile, data, 0644); err != nil { return destfile, nil, err } @@ -186,7 +190,9 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven } } provfile := destfile + ".prov" - if err := fileutil.AtomicWriteFile(provfile, body, 0644); err != nil { + + // Use PlatformAtomicWriteFile for the provenance file as well + if err := fileutil.PlatformAtomicWriteFile(provfile, body, 0644); err != nil { return destfile, nil, err } diff --git a/pkg/downloader/chart_downloader_windows_test.go b/pkg/downloader/chart_downloader_windows_test.go new file mode 100644 index 000000000..732416701 --- /dev/null +++ b/pkg/downloader/chart_downloader_windows_test.go @@ -0,0 +1,131 @@ +//go:build windows + +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package downloader + +import ( + "os" + "path/filepath" + "sync" + "testing" + + "helm.sh/helm/v4/pkg/cli" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/repo/v1/repotest" +) + +// TestParallelDownloadTo tests that parallel downloads to the same file +// don't cause "Access Denied" errors on Windows. This test is Windows-specific +// because the file locking behavior is only needed on Windows. +func TestParallelDownloadTo(t *testing.T) { + // Set up a simple test server with a chart + srv := repotest.NewTempServer(t, repotest.WithChartSourceGlob("testdata/*.tgz")) + defer srv.Stop() + + if err := srv.CreateIndex(); err != nil { + t.Fatal(err) + } + + dest := t.TempDir() + cacheDir := t.TempDir() + + c := ChartDownloader{ + Out: os.Stderr, + RepositoryConfig: repoConfig, + RepositoryCache: repoCache, + ContentCache: cacheDir, + Cache: &DiskCache{Root: cacheDir}, + Getters: getter.All(&cli.EnvSettings{ + RepositoryConfig: repoConfig, + RepositoryCache: repoCache, + ContentCache: cacheDir, + }), + } + + // Use a direct URL to bypass repository lookup + chartURL := srv.URL() + "/local-subchart-0.1.0.tgz" + + // Number of parallel downloads to attempt + numDownloads := 10 + var wg sync.WaitGroup + errors := make([]error, numDownloads) + + // Launch multiple goroutines to download the same chart simultaneously + for i := 0; i < numDownloads; i++ { + wg.Add(1) + go func(index int) { + defer wg.Done() + _, _, err := c.DownloadTo(chartURL, "", dest) + errors[index] = err + }(i) + } + + wg.Wait() + + // Check if any download failed + failedCount := 0 + for i, err := range errors { + if err != nil { + t.Logf("Download %d failed: %v", i, err) + failedCount++ + } + } + + // With the file locking fix, all parallel downloads should succeed + if failedCount > 0 { + t.Errorf("Parallel downloads failed: %d out of %d downloads failed due to concurrent file access", failedCount, numDownloads) + } + + // Verify the file exists and is valid + expectedFile := filepath.Join(dest, "local-subchart-0.1.0.tgz") + info, err := os.Stat(expectedFile) + if err != nil { + t.Errorf("Expected file %s does not exist: %v", expectedFile, err) + } else { + // Verify the file is not empty + if info.Size() == 0 { + t.Errorf("Downloaded file %s is empty (0 bytes)", expectedFile) + } + + // Verify the file has the expected size (should match the source file) + sourceFile := "testdata/local-subchart-0.1.0.tgz" + sourceInfo, err := os.Stat(sourceFile) + if err == nil && info.Size() != sourceInfo.Size() { + t.Errorf("Downloaded file size (%d bytes) doesn't match source file size (%d bytes)", + info.Size(), sourceInfo.Size()) + } + + // Verify it's a valid tar.gz file by checking the magic bytes + file, err := os.Open(expectedFile) + if err == nil { + defer file.Close() + // gzip magic bytes are 0x1f 0x8b + magic := make([]byte, 2) + if n, err := file.Read(magic); err == nil && n == 2 { + if magic[0] != 0x1f || magic[1] != 0x8b { + t.Errorf("Downloaded file is not a valid gzip file (magic bytes: %x)", magic) + } + } + } + + // Verify no lock file was left behind + lockFile := expectedFile + ".lock" + if _, err := os.Stat(lockFile); err == nil { + t.Errorf("Lock file %s was not cleaned up", lockFile) + } + } +} From 5638c35399464b6432ba81b92a341218991efa5c Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Sun, 8 Feb 2026 09:45:12 -0800 Subject: [PATCH 16/26] chore: new KEYS entry for George Jenkins Signed-off-by: George Jenkins --- KEYS | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/KEYS b/KEYS index e772fff40..21ca3487f 100644 --- a/KEYS +++ b/KEYS @@ -1058,3 +1058,23 @@ K6V08VpFmniENmCDHshXYq0gGiTDAP9FsXl2UtmFU5xuYxH4fRKIxgmxJRAFMWI8 u3Rdu/s+DQ== =smBO -----END PGP PUBLIC KEY BLOCK----- +pub ed25519 2026-02-08 [SC] + BF888333D96A1C18E2682AAED79D67C9EC016739 +uid [ultimate] George Jenkins +sig 3 D79D67C9EC016739 2026-02-08 [self-signature] +sub cv25519 2026-02-08 [E] +sig D79D67C9EC016739 2026-02-08 [self-signature] + +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEaYgDUBYJKwYBBAHaRw8BAQdAWKYkFrwgmfaY/hUq5Z2YpEy8WACKclo2eV/n +1ausaEy0JEdlb3JnZSBKZW5raW5zIDxndmplbmtpbnNAZ21haWwuY29tPoiTBBMW +CgA7FiEEv4iDM9lqHBjiaCqu151nyewBZzkFAmmIA1ACGwMFCwkIBwICIgIGFQoJ +CAsCBBYCAwECHgcCF4AACgkQ151nyewBZzlP0gD/ZFhm9FikdlZO5pW7xWR4YnP4 +yFAuY32G9dNdFn1x1p4BALR8Rtpp68eC9R8bq3/r1dK8gwig8DMWirdYaf2ePKoL +uDgEaYgDUBIKKwYBBAGXVQEFAQEHQJwM3R9CTypooHz/4w1waXAct8K2wA1bwi1r +yfb6uMMKAwEIB4h4BBgWCgAgFiEEv4iDM9lqHBjiaCqu151nyewBZzkFAmmIA1AC +GwwACgkQ151nyewBZzlgYAEAoVwYdoO6f3VwGukpv7RtKwF7PQC9AnBUx98TZZ6t +IaoA/RR14NXYYcd0fCwN6sFPq58/NbNkRHBrfw1CntxiJcYD +=duOC +-----END PGP PUBLIC KEY BLOCK----- From edbd705bd034246700cc0998016caa303cff42dc Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 9 Feb 2026 07:45:06 +0100 Subject: [PATCH 17/26] fix(cmd): errorlint linter #### Description errorlint linter in cmd/helm Signed-off-by: Matthieu MOREL --- cmd/helm/helm.go | 4 +++- cmd/helm/helm_test.go | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 66d342500..0c4f697b6 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -17,6 +17,7 @@ limitations under the License. package main // import "helm.sh/helm/v4/cmd/helm" import ( + "errors" "log/slog" "os" @@ -41,7 +42,8 @@ func main() { } if err := cmd.Execute(); err != nil { - if cerr, ok := err.(helmcmd.CommandError); ok { + var cerr helmcmd.CommandError + if errors.As(err, &cerr) { os.Exit(cerr.ExitCode) } os.Exit(1) diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index 0458e8037..60addadb1 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -18,6 +18,7 @@ package main import ( "bytes" + "errors" "os" "os/exec" "runtime" @@ -60,7 +61,8 @@ func TestCliPluginExitCode(t *testing.T) { cmd.Stderr = stderr err := cmd.Run() - exiterr, ok := err.(*exec.ExitError) + exiterr := &exec.ExitError{} + ok := errors.As(err, &exiterr) if !ok { t.Fatalf("Unexpected error type returned by os.Exit: %T", err) } From c8989d984ff69e8ad21b27d6ac6193dd3150b1a7 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 9 Feb 2026 07:45:21 +0100 Subject: [PATCH 18/26] fix(pkg): errorlint linter #### Description errorlint linter in pkg Signed-off-by: Matthieu MOREL --- pkg/action/package_test.go | 3 ++- pkg/cmd/upgrade.go | 3 ++- pkg/downloader/chart_downloader_test.go | 3 ++- pkg/repo/v1/index_test.go | 3 ++- pkg/storage/driver/sql_test.go | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go index 84dcb71c1..2e1d4ff07 100644 --- a/pkg/action/package_test.go +++ b/pkg/action/package_test.go @@ -17,6 +17,7 @@ limitations under the License. package action import ( + "errors" "os" "path" "testing" @@ -146,7 +147,7 @@ func TestValidateVersion(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := validateVersion(tt.args.ver); err != nil { - if err != tt.wantErr { + if !errors.Is(err, tt.wantErr) { t.Errorf("Expected {%v}, got {%v}", tt.wantErr, err) } diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index 918d6f5b8..b71c4ae2d 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -18,6 +18,7 @@ package cmd import ( "context" + "errors" "fmt" "io" "log" @@ -124,7 +125,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { histClient := action.NewHistory(cfg) histClient.Max = 1 versions, err := histClient.Run(args[0]) - if err == driver.ErrReleaseNotFound || isReleaseUninstalled(versions) { + if errors.Is(err, driver.ErrReleaseNotFound) || isReleaseUninstalled(versions) { // Only print this to stdout for table output if outfmt == output.Table { fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", args[0]) diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go index 4349ecef9..777e21a6e 100644 --- a/pkg/downloader/chart_downloader_test.go +++ b/pkg/downloader/chart_downloader_test.go @@ -18,6 +18,7 @@ package downloader import ( "crypto/sha256" "encoding/hex" + "errors" "os" "path/filepath" "testing" @@ -376,7 +377,7 @@ func TestScanReposForURL(t *testing.T) { // A lookup failure should produce an ErrNoOwnerRepo u = "https://no.such.repo/foo/bar-1.23.4.tgz" - if _, err = c.scanReposForURL(u, rf); err != ErrNoOwnerRepo { + if _, err = c.scanReposForURL(u, rf); !errors.Is(err, ErrNoOwnerRepo) { t.Fatalf("expected ErrNoOwnerRepo, got %v", err) } } diff --git a/pkg/repo/v1/index_test.go b/pkg/repo/v1/index_test.go index 517457dc4..446160143 100644 --- a/pkg/repo/v1/index_test.go +++ b/pkg/repo/v1/index_test.go @@ -20,6 +20,7 @@ import ( "bufio" "bytes" "encoding/json" + "errors" "fmt" "net/http" "os" @@ -639,7 +640,7 @@ func TestIgnoreSkippableChartValidationError(t *testing.T) { return } - if tc.Input != result { + if !errors.Is(tc.Input, result) { t.Error("expected the result equal to input") } diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go index f7c29033c..a1847e199 100644 --- a/pkg/storage/driver/sql_test.go +++ b/pkg/storage/driver/sql_test.go @@ -15,6 +15,7 @@ package driver import ( "database/sql/driver" + "errors" "fmt" "reflect" "regexp" @@ -447,7 +448,7 @@ func TestSqlQuery(t *testing.T) { _, err := sqlDriver.Query(labelSetUnknown) if err == nil { t.Errorf("Expected error {%v}, got nil", ErrReleaseNotFound) - } else if err != ErrReleaseNotFound { + } else if !errors.Is(err, ErrReleaseNotFound) { t.Fatalf("failed to query for unknown smug-pigeon release: %v", err) } From 4330bdea0409f428e75145f15532bfa0e2bc945c Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 9 Feb 2026 07:45:25 +0100 Subject: [PATCH 19/26] fix(pkg): errorlint linter #### Description errorlint linter in pkg Signed-off-by: Matthieu MOREL --- pkg/chart/common/capabilities.go | 2 +- pkg/cmd/template.go | 2 +- pkg/downloader/chart_downloader.go | 2 +- pkg/strvals/literal_parser.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/chart/common/capabilities.go b/pkg/chart/common/capabilities.go index 18d00de90..48d6e993f 100644 --- a/pkg/chart/common/capabilities.go +++ b/pkg/chart/common/capabilities.go @@ -157,7 +157,7 @@ func makeDefaultCapabilities() (*Capabilities, error) { v, err := semver.NewVersion(vstr) if err != nil { - return nil, fmt.Errorf("unable to parse k8s.io/client-go version %q: %v", vstr, err) + return nil, fmt.Errorf("unable to parse k8s.io/client-go version %q: %w", vstr, err) } kubeVersionMajor := v.Major() + 1 diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index 14f85042b..047fd60df 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -80,7 +80,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if kubeVersion != "" { parsedKubeVersion, err := common.ParseKubeVersion(kubeVersion) if err != nil { - return fmt.Errorf("invalid kube version '%s': %s", kubeVersion, err) + return fmt.Errorf("invalid kube version '%s': %w", kubeVersion, err) } client.KubeVersion = parsedKubeVersion } diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index ee4f8abe3..67e938448 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -380,7 +380,7 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (string, *url if err != nil { // If there is no special config, return the default HTTP client and // swallow the error. - if err == ErrNoOwnerRepo { + if errors.Is(err, ErrNoOwnerRepo) { // Make sure to add the ref URL as the URL for the getter c.Options = append(c.Options, getter.WithURL(ref)) return "", u, nil diff --git a/pkg/strvals/literal_parser.go b/pkg/strvals/literal_parser.go index d5d4c25b4..c2a824220 100644 --- a/pkg/strvals/literal_parser.go +++ b/pkg/strvals/literal_parser.go @@ -106,7 +106,7 @@ func (t *literalParser) key(data map[string]interface{}, nestedNameLevel int) (r case lastRune == '=': // found end of key: swallow the '=' and get the value value, err := t.val() - if err == nil && err != io.EOF { + if err == nil && !errors.Is(err, io.EOF) { return err } set(data, string(key), string(value)) From 025418291a7911441e7962895ba4bc24b72b55b3 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 9 Feb 2026 07:45:28 +0100 Subject: [PATCH 20/26] fix(internal): errorlint linter errorlint linter in internal/third_party/dep/fs Signed-off-by: Matthieu MOREL --- internal/chart/v3/util/dependencies.go | 4 +++- internal/plugin/runtime_subprocess_test.go | 4 +++- internal/third_party/dep/fs/fs.go | 3 ++- internal/third_party/dep/fs/fs_test.go | 5 +++-- internal/third_party/dep/fs/rename.go | 6 ++++-- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/internal/chart/v3/util/dependencies.go b/internal/chart/v3/util/dependencies.go index 5bf8a593c..9c4d8e80f 100644 --- a/internal/chart/v3/util/dependencies.go +++ b/internal/chart/v3/util/dependencies.go @@ -16,6 +16,7 @@ limitations under the License. package util import ( + "errors" "fmt" "log/slog" "strings" @@ -44,6 +45,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values, if len(c) > 0 { // retrieve value vv, err := cvals.PathValue(cpath + c) + var errNoValue common.ErrNoValue if err == nil { // if not bool, warn if bv, ok := vv.(bool); ok { @@ -51,7 +53,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values, break } slog.Warn("returned non-bool value", "path", c, "chart", r.Name) - } else if _, ok := err.(common.ErrNoValue); !ok { + } else if errors.As(err, &errNoValue) { // this is a real error slog.Warn("the method PathValue returned error", slog.Any("error", err)) } diff --git a/internal/plugin/runtime_subprocess_test.go b/internal/plugin/runtime_subprocess_test.go index ed251d28b..271e79661 100644 --- a/internal/plugin/runtime_subprocess_test.go +++ b/internal/plugin/runtime_subprocess_test.go @@ -16,6 +16,7 @@ limitations under the License. package plugin import ( + "errors" "fmt" "os" "path/filepath" @@ -76,7 +77,8 @@ func TestSubprocessPluginRuntime(t *testing.T) { }) require.Error(t, err) - ieerr, ok := err.(*InvokeExecError) + ieerr := &InvokeExecError{} + ok := errors.As(err, &ieerr) require.True(t, ok, "expected InvokeExecError, got %T", err) assert.Equal(t, 56, ieerr.ExitCode) diff --git a/internal/third_party/dep/fs/fs.go b/internal/third_party/dep/fs/fs.go index 6e2720f3b..3140cf91e 100644 --- a/internal/third_party/dep/fs/fs.go +++ b/internal/third_party/dep/fs/fs.go @@ -164,7 +164,8 @@ func CopyFile(src, dst string) (err error) { // // ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522): // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx - if lerr, ok := err.(*os.LinkError); ok && lerr.Err != syscall.Errno(1314) { + lerr := &os.LinkError{} + if errors.As(err, &lerr) && !errors.Is(lerr.Err, syscall.Errno(1314)) { return err } } else { diff --git a/internal/third_party/dep/fs/fs_test.go b/internal/third_party/dep/fs/fs_test.go index 610771bc3..8f28c3af7 100644 --- a/internal/third_party/dep/fs/fs_test.go +++ b/internal/third_party/dep/fs/fs_test.go @@ -32,6 +32,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package fs import ( + "errors" "os" "path/filepath" "runtime" @@ -234,7 +235,7 @@ func TestCopyDirFail_SrcIsNotDir(t *testing.T) { t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir) } - if err != errSrcNotDir { + if !errors.Is(err, errSrcNotDir) { t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errSrcNotDir, srcdir, dstdir, err) } @@ -260,7 +261,7 @@ func TestCopyDirFail_DstExists(t *testing.T) { t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir) } - if err != errDstExist { + if !errors.Is(err, errDstExist) { t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errDstExist, srcdir, dstdir, err) } } diff --git a/internal/third_party/dep/fs/rename.go b/internal/third_party/dep/fs/rename.go index 5f13b1ca3..77c93b7ad 100644 --- a/internal/third_party/dep/fs/rename.go +++ b/internal/third_party/dep/fs/rename.go @@ -34,6 +34,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package fs import ( + "errors" "fmt" "os" "syscall" @@ -46,10 +47,11 @@ func renameFallback(err error, src, dst string) error { // copy if we detect that case. syscall.EXDEV is the common name for the // cross device link error which has varying output text across different // operating systems. - terr, ok := err.(*os.LinkError) + terr := &os.LinkError{} + ok := errors.As(err, &terr) if !ok { return err - } else if terr.Err != syscall.EXDEV { + } else if !errors.Is(terr.Err, syscall.EXDEV) { return fmt.Errorf("link error: cannot rename %s to %s: %w", src, dst, terr) } From 259f76a849391e6ff60a9a2e95ce7310d958c602 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 9 Feb 2026 07:45:18 +0100 Subject: [PATCH 21/26] fix(pkg): errorlint linter errorlint linter in pkg/cmd Signed-off-by: Matthieu MOREL --- pkg/action/install.go | 4 ++-- pkg/chart/v2/util/dependencies.go | 4 +++- pkg/cmd/dependency_build.go | 4 +++- pkg/cmd/load_plugins.go | 4 +++- pkg/storage/driver/cfgmaps_test.go | 4 ++-- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index 0fe1f1a6e..7857b9035 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -488,7 +488,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource // pre-install hooks if !i.DisableHooks { if err := i.cfg.execHook(rel, release.HookPreInstall, i.WaitStrategy, i.WaitOptions, i.Timeout, i.ServerSideApply); err != nil { - return rel, fmt.Errorf("failed pre-install: %s", err) + return rel, fmt.Errorf("failed pre-install: %w", err) } } @@ -534,7 +534,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource if !i.DisableHooks { if err := i.cfg.execHook(rel, release.HookPostInstall, i.WaitStrategy, i.WaitOptions, i.Timeout, i.ServerSideApply); err != nil { - return rel, fmt.Errorf("failed post-install: %s", err) + return rel, fmt.Errorf("failed post-install: %w", err) } } diff --git a/pkg/chart/v2/util/dependencies.go b/pkg/chart/v2/util/dependencies.go index c7bb6621e..aa242e0ca 100644 --- a/pkg/chart/v2/util/dependencies.go +++ b/pkg/chart/v2/util/dependencies.go @@ -16,6 +16,7 @@ limitations under the License. package util import ( + "errors" "fmt" "log/slog" "strings" @@ -44,6 +45,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values, if len(c) > 0 { // retrieve value vv, err := cvals.PathValue(cpath + c) + var errNoValue common.ErrNoValue if err == nil { // if not bool, warn if bv, ok := vv.(bool); ok { @@ -51,7 +53,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values, break } slog.Warn("returned non-bool value", "path", c, "chart", r.Name) - } else if _, ok := err.(common.ErrNoValue); !ok { + } else if !errors.As(err, &errNoValue) { // this is a real error slog.Warn("the method PathValue returned error", slog.Any("error", err)) } diff --git a/pkg/cmd/dependency_build.go b/pkg/cmd/dependency_build.go index 7e5c731b7..b8ac16e60 100644 --- a/pkg/cmd/dependency_build.go +++ b/pkg/cmd/dependency_build.go @@ -16,6 +16,7 @@ limitations under the License. package cmd import ( + "errors" "fmt" "io" "os" @@ -76,7 +77,8 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command { man.Verify = downloader.VerifyIfPossible } err = man.Build() - if e, ok := err.(downloader.ErrRepoNotFound); ok { + var e downloader.ErrRepoNotFound + if errors.As(err, &e) { return fmt.Errorf("%s. Please add the missing repos via 'helm repo add'", e.Error()) } return err diff --git a/pkg/cmd/load_plugins.go b/pkg/cmd/load_plugins.go index 7bdbf597d..029dd04f5 100644 --- a/pkg/cmd/load_plugins.go +++ b/pkg/cmd/load_plugins.go @@ -18,6 +18,7 @@ package cmd import ( "bytes" "context" + "errors" "fmt" "io" "log/slog" @@ -120,7 +121,8 @@ func loadCLIPlugins(baseCmd *cobra.Command, out io.Writer) { Stderr: os.Stderr, } _, err = plug.Invoke(context.Background(), input) - if execErr, ok := err.(*plugin.InvokeExecError); ok { + execErr := &plugin.InvokeExecError{} + if errors.As(err, &execErr) { return CommandError{ error: execErr.Err, ExitCode: execErr.ExitCode, diff --git a/pkg/storage/driver/cfgmaps_test.go b/pkg/storage/driver/cfgmaps_test.go index 8beb45547..947ebff71 100644 --- a/pkg/storage/driver/cfgmaps_test.go +++ b/pkg/storage/driver/cfgmaps_test.go @@ -180,7 +180,7 @@ func TestConfigMapQuery(t *testing.T) { } _, err = cfgmaps.Query(map[string]string{"name": "notExist"}) - if err != ErrReleaseNotFound { + if !errors.Is(err, ErrReleaseNotFound) { t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) } } @@ -252,7 +252,7 @@ func TestConfigMapDelete(t *testing.T) { // perform the delete on a non-existent release _, err := cfgmaps.Delete("nonexistent") - if err != ErrReleaseNotFound { + if !errors.Is(err, ErrReleaseNotFound) { t.Fatalf("Expected ErrReleaseNotFound: got {%v}", err) } From 2e266c3ec9d70a6e656f8041bb31475e25e9eb22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 00:15:18 +0000 Subject: [PATCH 22/26] chore(deps): bump sigs.k8s.io/kustomize/kyaml from 0.21.0 to 0.21.1 Bumps [sigs.k8s.io/kustomize/kyaml](https://github.com/kubernetes-sigs/kustomize) from 0.21.0 to 0.21.1. - [Release notes](https://github.com/kubernetes-sigs/kustomize/releases) - [Commits](https://github.com/kubernetes-sigs/kustomize/compare/api/v0.21.0...api/v0.21.1) --- updated-dependencies: - dependency-name: sigs.k8s.io/kustomize/kyaml dependency-version: 0.21.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 92de0e40f..d4c52b192 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,7 @@ require ( k8s.io/kubectl v0.35.0 oras.land/oras-go/v2 v2.6.0 sigs.k8s.io/controller-runtime v0.23.1 - sigs.k8s.io/kustomize/kyaml v0.21.0 + sigs.k8s.io/kustomize/kyaml v0.21.1 sigs.k8s.io/yaml v1.6.0 ) diff --git a/go.sum b/go.sum index 46e86d3e3..c423bb221 100644 --- a/go.sum +++ b/go.sum @@ -509,8 +509,8 @@ sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5E sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= -sigs.k8s.io/kustomize/kyaml v0.21.0 h1:7mQAf3dUwf0wBerWJd8rXhVcnkk5Tvn/q91cGkaP6HQ= -sigs.k8s.io/kustomize/kyaml v0.21.0/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= +sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= +sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs= From 5cddc95bed0572b8d63a910843f0a70477a4ce33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 00:15:29 +0000 Subject: [PATCH 23/26] chore(deps): bump golang.org/x/text from 0.33.0 to 0.34.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.33.0 to 0.34.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.33.0...v0.34.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.34.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 92de0e40f..d3b24bb9a 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.47.0 golang.org/x/term v0.39.0 - golang.org/x/text v0.33.0 + golang.org/x/text v0.34.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.35.0 k8s.io/apiextensions-apiserver v0.35.0 @@ -157,13 +157,13 @@ require ( go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/mod v0.31.0 // indirect - golang.org/x/net v0.48.0 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.40.0 // indirect + golang.org/x/tools v0.41.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect google.golang.org/grpc v1.72.2 // indirect diff --git a/go.sum b/go.sum index 46e86d3e3..45f429f8d 100644 --- a/go.sum +++ b/go.sum @@ -383,8 +383,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= -golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -395,8 +395,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -444,8 +444,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -454,8 +454,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= From a23b6388ac74984460fd4055de5120d2fc03d841 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 21:34:40 +0000 Subject: [PATCH 24/26] chore(deps): bump github.com/lib/pq from 1.11.1 to 1.11.2 Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.11.1 to 1.11.2. - [Release notes](https://github.com/lib/pq/releases) - [Changelog](https://github.com/lib/pq/blob/master/CHANGELOG.md) - [Commits](https://github.com/lib/pq/compare/v1.11.1...v1.11.2) --- updated-dependencies: - dependency-name: github.com/lib/pq dependency-version: 1.11.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 92de0e40f..d03530bba 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/gofrs/flock v0.13.0 github.com/gosuri/uitable v0.0.4 github.com/jmoiron/sqlx v1.4.0 - github.com/lib/pq v1.11.1 + github.com/lib/pq v1.11.2 github.com/mattn/go-shellwords v1.0.12 github.com/moby/term v0.5.2 github.com/opencontainers/go-digest v1.0.0 diff --git a/go.sum b/go.sum index 46e86d3e3..b00569302 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI= -github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= +github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= From aec7aced38d053a2df5d0973abdf21848778a722 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 05:09:52 +0000 Subject: [PATCH 25/26] chore(deps): bump golang.org/x/term from 0.39.0 to 0.40.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.39.0 to 0.40.0. - [Commits](https://github.com/golang/term/compare/v0.39.0...v0.40.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-version: 0.40.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 4aaeb932a..ac9bce44a 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/tetratelabs/wazero v1.11.0 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.47.0 - golang.org/x/term v0.39.0 + golang.org/x/term v0.40.0 golang.org/x/text v0.34.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.35.0 @@ -161,7 +161,7 @@ require ( golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect + golang.org/x/sys v0.41.0 // indirect golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.41.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect diff --git a/go.sum b/go.sum index 9b22a40c5..eba8fe939 100644 --- a/go.sum +++ b/go.sum @@ -426,8 +426,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -435,8 +435,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= From 782385397ce1871f4c8a4d2e3c857937bd8988c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 05:32:54 +0000 Subject: [PATCH 26/26] chore(deps): bump golang.org/x/crypto from 0.47.0 to 0.48.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.47.0 to 0.48.0. - [Commits](https://github.com/golang/crypto/compare/v0.47.0...v0.48.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.48.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ac9bce44a..2a7787076 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/tetratelabs/wazero v1.11.0 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.47.0 + golang.org/x/crypto v0.48.0 golang.org/x/term v0.40.0 golang.org/x/text v0.34.0 gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index eba8fe939..35b74a320 100644 --- a/go.sum +++ b/go.sum @@ -377,8 +377,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=