diff --git a/go.mod b/go.mod index 6db35856b..b9322a4d1 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/lib/pq v1.10.9 github.com/mattn/go-shellwords v1.0.12 github.com/mitchellh/copystructure v1.2.0 - github.com/moby/term v0.5.2 + github.com/moby/term v0.5.1 github.com/opencontainers/image-spec v1.1.0 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 266ca3add..06c48b43d 100644 --- a/go.sum +++ b/go.sum @@ -263,8 +263,8 @@ github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vyg github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= -github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/moby/term v0.5.1 h1:iy+9Sd2jzMs24S0frhx8fMvcw0/5C5LxVU0eEqOWoRs= +github.com/moby/term v0.5.1/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/pkg/kube/fake/fake.go b/pkg/kube/fake/fake.go index a60d9f34e..086afe6e4 100644 --- a/pkg/kube/fake/fake.go +++ b/pkg/kube/fake/fake.go @@ -158,3 +158,5 @@ func createDummyResourceList() kube.ResourceList { return resourceList } + + diff --git a/pkg/kube/fake/fake_test.go b/pkg/kube/fake/fake_test.go new file mode 100644 index 000000000..74a9123e0 --- /dev/null +++ b/pkg/kube/fake/fake_test.go @@ -0,0 +1,141 @@ +/* +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 fake + +import ( + "errors" + "os" + "testing" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/cli-runtime/pkg/resource" + + "helm.sh/helm/v4/pkg/kube" +) + +func TestFailingKubeClient_UpdateWithTakeOwnership(t *testing.T) { + // Creating a dummy resource with existing ownership + existingResource := &resource.Info{ + Name: "existing-resource", + Namespace: "default", + Object: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "existing-resource", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "ReplicaSet", + Name: "original-owner", + UID: "123", + }, + }, + }, + }, + } + + // Creating modified resource with new ownership + modifiedResource := &resource.Info{ + Name: "existing-resource", + Namespace: "default", + Object: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "existing-resource", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Helm", + Name: "new-owner", + UID: "456", + }, + }, + }, + }, + } + + tests := []struct { + name string + takeOwnership bool + updateError error + wantError bool + }{ + { + name: "successful take ownership", + takeOwnership: true, + updateError: nil, + wantError: false, + }, + { + name: "ownership not taken when flag is false", + takeOwnership: false, + updateError: nil, + wantError: false, + }, + { + name: "update error with take ownership", + takeOwnership: true, + updateError: errors.New("update failed"), + wantError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := &FailingKubeClient{ + PrintingKubeClient: PrintingKubeClient{ + Out: os.Stdout, + }, + UpdateError: tt.updateError, + } + + existingList := kube.ResourceList{existingResource} + modifiedList := kube.ResourceList{modifiedResource} + result, err := client.Update(existingList, modifiedList, tt.takeOwnership) + + if tt.wantError && err == nil { + t.Error("expected error, got none") + } + if !tt.wantError && err != nil { + t.Errorf("unexpected error: %v", err) + } + + if err == nil { + if tt.takeOwnership { + modifiedPod := modifiedList[0].Object.(*v1.Pod) + if len(modifiedPod.OwnerReferences) != 1 { + t.Error("expected exactly one owner reference") + } + if ownerRef := modifiedPod.OwnerReferences[0]; ownerRef.Kind != "Helm" { + t.Errorf("expected owner kind to be Helm, got %s", ownerRef.Kind) + } + } + if !tt.takeOwnership { + originalPod := existingList[0].Object.(*v1.Pod) + if len(originalPod.OwnerReferences) != 1 { + t.Error("expected exactly one owner reference") + } + if ownerRef := originalPod.OwnerReferences[0]; ownerRef.Kind != "ReplicaSet" { + t.Errorf("expected owner kind to be ReplicaSet, got %s", ownerRef.Kind) + } + } + if result == nil { + t.Error("expected non-nil result") + } + } + }) + } +} diff --git a/pkg/testutils/helm_test.go b/pkg/testutils/helm_test.go new file mode 100644 index 000000000..a3bde2a17 --- /dev/null +++ b/pkg/testutils/helm_test.go @@ -0,0 +1,83 @@ +/* +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 testutils + +import ( + "context" + "os" + "os/exec" + "testing" + + v1 "k8s.io/api/core/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +func RunHelmCommand(t *testing.T, args []string) (string, error) { + t.Helper() + + kubeConfig := os.Getenv("KUBECONFIG") + if kubeConfig == "" { + t.Fatal("KUBECONFIG environment variable is not set") + } + + cmd := exec.Command("helm", args...) + cmd.Env = append(os.Environ(), "KUBECONFIG="+kubeConfig) + + output, err := cmd.CombinedOutput() + if err != nil { + t.Logf("Helm command failed: %s", string(output)) + return string(output), err + } + + return string(output), nil +} + +// Test for `helm upgrade --install --take-ownership` +func TestTakeOwnershipFlag(t *testing.T) { + ns, cleanup := SetupTestEnvironment(t) + defer cleanup() + client := fake.NewSimpleClientset() + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example-pod", + Namespace: ns, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Helm", + Name: "test-release", + UID: "456", + }, + }, + }, + } + + _, err := client.CoreV1().Pods(ns).Create(context.Background(), pod, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("failed to create pod: %v", err) + } + retrievedPod, err := client.CoreV1().Pods(ns).Get(context.Background(), "example-pod", metav1.GetOptions{}) + if err != nil { + t.Fatalf("failed to retrieve pod: %v", err) + } + + if len(retrievedPod.OwnerReferences) != 1 || retrievedPod.OwnerReferences[0].Kind != "Helm" { + t.Errorf("expected owner kind to be Helm, got %v", retrievedPod.OwnerReferences[0].Kind) + } +} diff --git a/pkg/testutils/setup_test.go b/pkg/testutils/setup_test.go new file mode 100644 index 000000000..994763fef --- /dev/null +++ b/pkg/testutils/setup_test.go @@ -0,0 +1,46 @@ +/* +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 testutils +import ( + "context" + "testing" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +// SetupTestEnvironment creates a test namespace using a fake client and returns a cleanup function +func SetupTestEnvironment(t *testing.T) (string, func()) { + t.Helper() + client := fake.NewSimpleClientset() + ns := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "helm-test-", + }, + } + + ns, err := client.CoreV1().Namespaces().Create(context.Background(), ns, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("failed to create namespace: %v", err) + } + + return ns.Name, func() { + if err := client.CoreV1().Namespaces().Delete(context.Background(), ns.Name, metav1.DeleteOptions{}); err != nil { + t.Logf("failed to clean up namespace: %v", err) + } + } +}