From 343fd896bc8894d1f799cc75dcbd599baeed8903 Mon Sep 17 00:00:00 2001 From: Karthik Rajan Date: Thu, 15 Oct 2026 21:48:44 +0530 Subject: [PATCH 1/3] prevent panic in RemoveNodePool when slice is empty Signed-off-by: Karthik Rajan --- utility/kubernetes.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utility/kubernetes.go b/utility/kubernetes.go index 2378ec2c0..1226dd0cb 100644 --- a/utility/kubernetes.go +++ b/utility/kubernetes.go @@ -282,6 +282,9 @@ func checkKubeDir() { // RemoveNodePool is a utility function to remove node pool from a kuberentes cluster func RemoveNodePool(s []civogo.KubernetesClusterPoolConfig, id string, names []string) ([]civogo.KubernetesClusterPoolConfig, []string) { + if len(s) == 0 { + return s, names + } key := 0 for k, v := range s { if strings.Contains(v.ID, id) { From 26c496a54d18b23f7cd2d5431a90fbc0acceee86 Mon Sep 17 00:00:00 2001 From: Karthik Rajan Date: Sun, 1 Nov 2026 22:01:33 +0530 Subject: [PATCH 2/3] refactor: update kubernetes utility functions and add documentation and issues files Signed-off-by: Karthik Rajan --- utility/kubernetes.go | 8 +++- utility/kubernetes_test.go | 98 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/utility/kubernetes.go b/utility/kubernetes.go index 1226dd0cb..d1be924c3 100644 --- a/utility/kubernetes.go +++ b/utility/kubernetes.go @@ -285,15 +285,19 @@ func RemoveNodePool(s []civogo.KubernetesClusterPoolConfig, id string, names []s if len(s) == 0 { return s, names } - key := 0 + key := -1 for k, v := range s { - if strings.Contains(v.ID, id) { + if strings.HasPrefix(v.ID, id) { key = k names = append(names, v.ID) break } } + if key == -1 { + return s, names + } + s[len(s)-1], s[key] = s[key], s[len(s)-1] return s[:len(s)-1], names } diff --git a/utility/kubernetes_test.go b/utility/kubernetes_test.go index a7939ef6d..bf7f7bcee 100644 --- a/utility/kubernetes_test.go +++ b/utility/kubernetes_test.go @@ -1,6 +1,7 @@ package utility import ( + "slices" "testing" "github.com/civo/civogo" @@ -72,6 +73,103 @@ func TestRemoveApplicationFromInstalledListWithMultiple(t *testing.T) { // } // } +func TestRemoveNodePool(t *testing.T) { + pools := func(ids ...string) []civogo.KubernetesClusterPoolConfig { + s := []civogo.KubernetesClusterPoolConfig{} + for _, id := range ids { + s = append(s, civogo.KubernetesClusterPoolConfig{ID: id}) + } + return s + } + + tests := []struct { + name string + pools []civogo.KubernetesClusterPoolConfig + remove []string + wantPools []string + wantRemove []string + }{ + { + name: "removes the first pool", + pools: pools("aaaaaa-1", "bbbbbb-2", "cccccc-3"), + remove: []string{"aaaaaa"}, + wantPools: []string{"bbbbbb-2", "cccccc-3"}, + wantRemove: []string{"aaaaaa-1"}, + }, + { + name: "removes a middle pool", + pools: pools("aaaaaa-1", "bbbbbb-2", "cccccc-3"), + remove: []string{"bbbbbb"}, + wantPools: []string{"aaaaaa-1", "cccccc-3"}, + wantRemove: []string{"bbbbbb-2"}, + }, + { + name: "removes the last pool", + pools: pools("aaaaaa-1", "bbbbbb-2", "cccccc-3"), + remove: []string{"cccccc"}, + wantPools: []string{"aaaaaa-1", "bbbbbb-2"}, + wantRemove: []string{"cccccc-3"}, + }, + { + name: "empty slice does not panic", + pools: pools(), + remove: []string{"aaaaaa"}, + wantPools: []string{}, + wantRemove: []string{}, + }, + { + name: "unknown ID leaves pools unchanged", + pools: pools("aaaaaa-1", "bbbbbb-2"), + remove: []string{"zzzzzz"}, + wantPools: []string{"aaaaaa-1", "bbbbbb-2"}, + wantRemove: []string{}, + }, + { + name: "same ID twice removes only the matching pool", + pools: pools("aaaaaa-1", "bbbbbb-2", "cccccc-3"), + remove: []string{"bbbbbb", "bbbbbb"}, + wantPools: []string{"aaaaaa-1", "cccccc-3"}, + wantRemove: []string{"bbbbbb-2"}, + }, + { + name: "ID fragment in the middle of another ID does not match", + pools: pools("aaaaaa-bbbbbb", "cccccc-1"), + remove: []string{"bbbbbb"}, + wantPools: []string{"aaaaaa-bbbbbb", "cccccc-1"}, + wantRemove: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("RemoveNodePool panicked: %v", r) + } + }() + + got := tt.pools + names := []string{} + for _, id := range tt.remove { + got, names = RemoveNodePool(got, id, names) + } + + gotIDs := []string{} + for _, p := range got { + gotIDs = append(gotIDs, p.ID) + } + slices.Sort(gotIDs) + + if !slices.Equal(gotIDs, tt.wantPools) { + t.Errorf("remaining pools = %v, want %v", gotIDs, tt.wantPools) + } + if !slices.Equal(names, tt.wantRemove) { + t.Errorf("removed names = %v, want %v", names, tt.wantRemove) + } + }) + } +} + func TestIsAppCompatibleWithClusterType(t *testing.T) { tests := []struct { name string From 7d5bd8dfce3e2f9217840ab33c64e3298e5102e8 Mon Sep 17 00:00:00 2001 From: Karthik Rajan Date: Mon, 2 Nov 2026 22:45:30 +0530 Subject: [PATCH 3/3] refactor: remove redundant slice length check in RemoveNodePool and add feature/issue documentation files Signed-off-by: Karthik Rajan --- cmd/kubernetes/kubernetes_nodepool_delete.go | 16 +++++++++++++--- utility/kubernetes.go | 3 --- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cmd/kubernetes/kubernetes_nodepool_delete.go b/cmd/kubernetes/kubernetes_nodepool_delete.go index af2c42dfb..5992e461e 100644 --- a/cmd/kubernetes/kubernetes_nodepool_delete.go +++ b/cmd/kubernetes/kubernetes_nodepool_delete.go @@ -69,8 +69,18 @@ var kubernetesNodePoolDeleteCmd = &cobra.Command{ } kubernetesPoolNameList = nil + notFound := []string{} for _, kubeList := range kubernetesNodePoolList { + before := len(kubernetesPoolNameList) nodePool, kubernetesPoolNameList = utility.RemoveNodePool(nodePool, kubeList.Name, kubernetesPoolNameList) + if len(kubernetesPoolNameList) == before { + notFound = append(notFound, kubeList.Name) + } + } + + if len(notFound) > 0 { + utility.Error("No node pool found matching %s in cluster %s", strings.Join(notFound, ", "), kubernetesFindCluster.Name) + os.Exit(1) } configKubernetes := &civogo.KubernetesClusterConfig{ @@ -85,14 +95,14 @@ var kubernetesNodePoolDeleteCmd = &cobra.Command{ ow := utility.NewOutputWriter() - for _, v := range kubernetesNodePoolList { + for _, v := range kubernetesPoolNameList { ow.StartLine() - ow.AppendDataWithLabel("id", v.Name, "ID") + ow.AppendDataWithLabel("id", v, "ID") } switch common.OutputFormat { case "json": - if len(kubernetesNodePoolList) == 1 { + if len(kubernetesPoolNameList) == 1 { ow.WriteSingleObjectJSON(common.PrettySet) } else { ow.WriteMultipleObjectsJSON(common.PrettySet) diff --git a/utility/kubernetes.go b/utility/kubernetes.go index d1be924c3..e771c73c7 100644 --- a/utility/kubernetes.go +++ b/utility/kubernetes.go @@ -282,9 +282,6 @@ func checkKubeDir() { // RemoveNodePool is a utility function to remove node pool from a kuberentes cluster func RemoveNodePool(s []civogo.KubernetesClusterPoolConfig, id string, names []string) ([]civogo.KubernetesClusterPoolConfig, []string) { - if len(s) == 0 { - return s, names - } key := -1 for k, v := range s { if strings.HasPrefix(v.ID, id) {