diff --git a/README.md b/README.md index 31aec8e9..3432a392 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ Kor provides various subcommands to identify and list unused resources. The avai - `daemonset`- Gets unused DaemonSets for the specified namespace or all namespaces. - `finalizer` - Gets unused pending deletion resources for the specified namespace or all namespaces. - `networkpolicy` - Gets unused NetworkPolicies for the specified namespace or all namespaces. +- `argorollouts` - Gets unused Argo Rolouts for the specified namespace or all namespaces. - `exporter` - Export Prometheus metrics. - `version` - Print kor version information. @@ -169,7 +170,7 @@ kor [subcommand] --help | ConfigMaps | ConfigMaps not used in the following places:
- Pods
- Containers
- ConfigMaps used through Volumes
- ConfigMaps used through environment variables | ConfigMaps used by resources which don't explicitly state them in the config.
e.g Grafana dashboards loaded dynamically OPA policies fluentd configs CRD configs | | Secrets | Secrets not used in the following places:
- Pods
- Containers
- Secrets used through volumes
- Secrets used through environment variables
- Secrets used by Ingress TLS
- Secrets used by ServiceAccounts | Secrets used by resources which don't explicitly state them in the config e.g. secrets used by CRDs | | Services | Services with no endpoints | | -| Deployments | Deployments with no Replicas | | +| Deployments | Deployments with no Replicas and non existent ArgoRollout Owner (WorkloadRef) | | | ServiceAccounts | ServiceAccounts unused by Pods
ServiceAccounts unused by roleBinding or clusterRoleBinding | | | StatefulSets | Statefulsets with no Replicas | | | Roles | Roles not used in roleBinding | | @@ -181,11 +182,12 @@ kor [subcommand] --help | CRDs | CRDs not used the cluster | | | Pvs | PVs not bound to a PVC | | | Pdbs | PDBs not used in Deployments
PDBs not used in StatefulSets | | -| Jobs | Jobs status is completed
Jobs status is suspended
Jobs failed with backoff limit exceeded (including indexed jobs)
Jobs failed with dedaline exceeded | | -| ReplicaSets | replicaSets that specify replicas to 0 and has already completed it's work | -| DaemonSets | DaemonSets not scheduled on any nodes | -| StorageClasses | StorageClasses not used by any PVs/PVCs | -| NetworkPolicies | NetworkPolicies with no Pods selected by podSelector or Ingress/Egress rules | +| Jobs | Jobs status is completed
Jobs status is suspended
Jobs failed with backoff limit exceeded (including indexed jobs)
Jobs failed with dedaline exceeded | | +| ReplicaSets | replicaSets that specify replicas to 0 and has already completed it's work | | +| DaemonSets | DaemonSets not scheduled on any nodes | | +| StorageClasses | StorageClasses not used by any PVs/PVCs | | +| NetworkPolicies | NetworkPolicies with no Pods selected by podSelector or Ingress/Egress rules | | +| ArgoRollout | Argo Rollouts with WorkloadRef deployment non existent in cluster | When deployment from Argo Rollouts has deleted, replicasets keep pods alive, attention to this | ### Deleting Unused resources @@ -250,6 +252,7 @@ Unused resources in namespace: "test" | 6 | ConfigMap | unused-cm | ConfigMap is not used in any pod or container | | 7 | ServiceAccount | my-service-account2 | ServiceAccount is not in use | | 8 | Pdb | my-pdb | Pdb is not referencing any deployments or statefulsets | +| 9 | ArgoRollout | rollout-ref-deployment | Rollout has no deployments | +---+----------------+----------------------------------------------+--------------------------------------------------------+ ``` @@ -299,13 +302,14 @@ Unused resources in namespace: "ns1" | 4 | Deployment | deploy1 | +---+---------------+--------------------+ Unused resources in namespace: "ns2" -+---+---------------+--------------------+ -| # | RESOURCE TYPE | RESOURCE NAME | -+---+---------------+--------------------+ -| 1 | ReplicaSet | deploy2-79f48888c6 | -| 2 | ConfigMap | cm3 | -| 3 | Deployment | deploy2 | -+---+---------------+--------------------+ ++---+---------------+-------------------------+ +| # | RESOURCE TYPE | RESOURCE NAME | ++---+---------------+-------------------------+ +| 1 | ReplicaSet | deploy2-79f48888c6 | +| 2 | ConfigMap | cm3 | +| 3 | Deployment | deploy2 | +| 4 | ArgoRollout | rollout-ref-deployment | ++---+---------------+-------------------------+ ``` ## In Cluster Usage diff --git a/charts/kor/templates/role.yaml b/charts/kor/templates/role.yaml index a1d73f12..0cb46b22 100644 --- a/charts/kor/templates/role.yaml +++ b/charts/kor/templates/role.yaml @@ -26,6 +26,7 @@ rules: - replicasets - daemonsets - networkpolicies + - rollouts verbs: - get - list @@ -58,6 +59,7 @@ rules: - replicasets - daemonsets - networkpolicies + - rollouts {{/* cluster-scoped resources */}} - namespaces - clusterroles diff --git a/cmd/kor/all.go b/cmd/kor/all.go index 3478c9be..ac12a7a4 100644 --- a/cmd/kor/all.go +++ b/cmd/kor/all.go @@ -15,10 +15,11 @@ var allCmd = &cobra.Command{ Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { clientset := kor.GetKubeClient(kubeconfig) + clientsetargorollouts := kor.GetKubeClientArgoRollouts(kubeconfig) apiExtClient := kor.GetAPIExtensionsClient(kubeconfig) dynamicClient := kor.GetDynamicClient(kubeconfig) - if response, err := kor.GetUnusedAll(filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts); err != nil { + if response, err := kor.GetUnusedAll(filterOptions, clientset, clientsetargorollouts, apiExtClient, dynamicClient, outputFormat, opts); err != nil { fmt.Println(err) } else { utils.PrintLogo(outputFormat) diff --git a/cmd/kor/argorollouts.go b/cmd/kor/argorollouts.go new file mode 100644 index 00000000..14f84682 --- /dev/null +++ b/cmd/kor/argorollouts.go @@ -0,0 +1,31 @@ +package kor + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/yonahd/kor/pkg/kor" + "github.com/yonahd/kor/pkg/utils" +) + +var argoRolloutsCmd = &cobra.Command{ + Use: "argorollouts", + Aliases: []string{"argorollouts"}, + Short: "Gets unused argo rollouts", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + clientset := kor.GetKubeClient(kubeconfig) + clientsetargorollouts := kor.GetKubeClientArgoRollouts(kubeconfig) + if response, err := kor.GetUnusedArgoRollouts(filterOptions, clientset, clientsetargorollouts, outputFormat, opts); err != nil { + fmt.Println(err) + } else { + utils.PrintLogo(outputFormat) + fmt.Println(response) + } + }, +} + +func init() { + rootCmd.AddCommand(argoRolloutsCmd) +} diff --git a/cmd/kor/deployments.go b/cmd/kor/deployments.go index 4eb16d6d..621dfaab 100644 --- a/cmd/kor/deployments.go +++ b/cmd/kor/deployments.go @@ -16,7 +16,8 @@ var deployCmd = &cobra.Command{ Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { clientset := kor.GetKubeClient(kubeconfig) - if response, err := kor.GetUnusedDeployments(filterOptions, clientset, outputFormat, opts); err != nil { + clientsetargorollouts := kor.GetKubeClientArgoRollouts(kubeconfig) + if response, err := kor.GetUnusedDeployments(filterOptions, clientset, clientsetargorollouts, outputFormat, opts); err != nil { fmt.Println(err) } else { utils.PrintLogo(outputFormat) diff --git a/cmd/kor/exporter.go b/cmd/kor/exporter.go index bb245124..d053a108 100644 --- a/cmd/kor/exporter.go +++ b/cmd/kor/exporter.go @@ -14,10 +14,11 @@ var exporterCmd = &cobra.Command{ Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { clientset := kor.GetKubeClient(kubeconfig) + clientsetargorollouts := kor.GetKubeClientArgoRollouts(kubeconfig) apiExtClient := kor.GetAPIExtensionsClient(kubeconfig) dynamicClient := kor.GetDynamicClient(kubeconfig) - kor.Exporter(filterOptions, clientset, apiExtClient, dynamicClient, "json", opts, resourceList) + kor.Exporter(filterOptions, clientset, clientsetargorollouts, apiExtClient, dynamicClient, "json", opts, resourceList) }, } diff --git a/cmd/kor/root.go b/cmd/kor/root.go index 17c818e9..bc139407 100644 --- a/cmd/kor/root.go +++ b/cmd/kor/root.go @@ -32,10 +32,11 @@ var rootCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { resourceNames := args[0] clientset := kor.GetKubeClient(kubeconfig) + clientsetargorollouts := kor.GetKubeClientArgoRollouts(kubeconfig) apiExtClient := kor.GetAPIExtensionsClient(kubeconfig) dynamicClient := kor.GetDynamicClient(kubeconfig) - if response, err := kor.GetUnusedMulti(resourceNames, filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts); err != nil { + if response, err := kor.GetUnusedMulti(resourceNames, filterOptions, clientset, clientsetargorollouts, apiExtClient, dynamicClient, outputFormat, opts); err != nil { fmt.Println(err) } else { utils.PrintLogo(outputFormat) diff --git a/go.mod b/go.mod index 37bc1300..88832649 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22.0 toolchain go1.22.2 require ( + github.com/argoproj/argo-rollouts v1.7.2 github.com/fatih/color v1.17.0 github.com/olekukonko/tablewriter v0.0.5 github.com/prometheus/client_golang v1.20.4 diff --git a/go.sum b/go.sum index b384cd58..cffa2d6f 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/argoproj/argo-rollouts v1.7.2 h1:faDUH/qePerYRwsrHfVzNQkhjGBgXIiVYdVK8824kMo= +github.com/argoproj/argo-rollouts v1.7.2/go.mod h1:Te4HrUELxKiBpK8lgk77o4gTa3mv8pXCd8xdPprKrbs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= diff --git a/pkg/kor/all.go b/pkg/kor/all.go index ffaad22b..8b22c1cd 100644 --- a/pkg/kor/all.go +++ b/pkg/kor/all.go @@ -6,6 +6,7 @@ import ( "fmt" "os" + "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned" apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" @@ -72,8 +73,8 @@ func getUnusedServiceAccounts(clientset kubernetes.Interface, namespace string, return namespaceSADiff } -func getUnusedDeployments(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff { - deployDiff, err := processNamespaceDeployments(clientset, namespace, filterOpts) +func getUnusedDeployments(clientset kubernetes.Interface, clientsetargorollouts versioned.Interface, namespace string, filterOpts *filters.Options) ResourceDiff { + deployDiff, err := processNamespaceDeployments(clientset, clientsetargorollouts, namespace, filterOpts) if err != nil { fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "deployments", namespace, err) } @@ -277,7 +278,19 @@ func getUnusedRoleBindings(clientset kubernetes.Interface, namespace string, fil return namespaceRoleBindingDiff } -func GetUnusedAllNamespaced(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts common.Opts) (string, error) { +func getUnusedArgoRollouts(clientset kubernetes.Interface, clientsetargorollouts versioned.Interface, namespace string, filterOpts *filters.Options) ResourceDiff { + argoRolloutsDiff, err := processNamespaceArgoRollouts(clientset, clientsetargorollouts, namespace, filterOpts) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "argorollouts", namespace, err) + } + namespaceSADiff := ResourceDiff{ + "ArgoRollout", + argoRolloutsDiff, + } + return namespaceSADiff +} + +func GetUnusedAllNamespaced(filterOpts *filters.Options, clientset kubernetes.Interface, clientsetargorollouts versioned.Interface, outputFormat string, opts common.Opts) (string, error) { resources := make(map[string]map[string][]ResourceInfo) for _, namespace := range filterOpts.Namespaces(clientset) { switch opts.GroupBy { @@ -287,7 +300,8 @@ func GetUnusedAllNamespaced(filterOpts *filters.Options, clientset kubernetes.In resources[namespace]["Service"] = getUnusedSVCs(clientset, namespace, filterOpts).diff resources[namespace]["Secret"] = getUnusedSecrets(clientset, namespace, filterOpts).diff resources[namespace]["ServiceAccount"] = getUnusedServiceAccounts(clientset, namespace, filterOpts).diff - resources[namespace]["Deployment"] = getUnusedDeployments(clientset, namespace, filterOpts).diff + resources[namespace]["Deployment"] = getUnusedDeployments(clientset, clientsetargorollouts, namespace, filterOpts).diff + resources[namespace]["ArgoRollout"] = getUnusedArgoRollouts(clientset, clientsetargorollouts, namespace, filterOpts).diff resources[namespace]["StatefulSet"] = getUnusedStatefulSets(clientset, namespace, filterOpts).diff resources[namespace]["Role"] = getUnusedRoles(clientset, namespace, filterOpts).diff resources[namespace]["Hpa"] = getUnusedHpas(clientset, namespace, filterOpts).diff @@ -305,7 +319,8 @@ func GetUnusedAllNamespaced(filterOpts *filters.Options, clientset kubernetes.In appendResources(resources, "Service", namespace, getUnusedSVCs(clientset, namespace, filterOpts).diff) appendResources(resources, "Secret", namespace, getUnusedSecrets(clientset, namespace, filterOpts).diff) appendResources(resources, "ServiceAccount", namespace, getUnusedServiceAccounts(clientset, namespace, filterOpts).diff) - appendResources(resources, "Deployment", namespace, getUnusedDeployments(clientset, namespace, filterOpts).diff) + appendResources(resources, "Deployment", namespace, getUnusedDeployments(clientset, clientsetargorollouts, namespace, filterOpts).diff) + appendResources(resources, "ArgoRollout", namespace, getUnusedArgoRollouts(clientset, clientsetargorollouts, namespace, filterOpts).diff) appendResources(resources, "StatefulSet", namespace, getUnusedStatefulSets(clientset, namespace, filterOpts).diff) appendResources(resources, "Role", namespace, getUnusedRoles(clientset, namespace, filterOpts).diff) appendResources(resources, "Hpa", namespace, getUnusedHpas(clientset, namespace, filterOpts).diff) @@ -377,8 +392,8 @@ func GetUnusedAllNonNamespaced(filterOpts *filters.Options, clientset kubernetes return unusedAllNonNamespaced, nil } -func GetUnusedAll(filterOpts *filters.Options, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts common.Opts) (string, error) { - unusedAllNamespaced, err := GetUnusedAllNamespaced(filterOpts, clientset, outputFormat, opts) +func GetUnusedAll(filterOpts *filters.Options, clientset kubernetes.Interface, clientsetargorollouts versioned.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts common.Opts) (string, error) { + unusedAllNamespaced, err := GetUnusedAllNamespaced(filterOpts, clientset, clientsetargorollouts, outputFormat, opts) if err != nil { fmt.Printf("err: %v\n", err) } diff --git a/pkg/kor/argorollouts.go b/pkg/kor/argorollouts.go new file mode 100644 index 00000000..8b040570 --- /dev/null +++ b/pkg/kor/argorollouts.go @@ -0,0 +1,92 @@ +package kor + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + + "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + "github.com/yonahd/kor/pkg/common" + "github.com/yonahd/kor/pkg/filters" +) + +func processNamespaceArgoRollouts(clientset kubernetes.Interface, clientsetrollout versioned.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) { + argoRolloutList, err := clientsetrollout.ArgoprojV1alpha1().Rollouts(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels}) + + if err != nil { + return nil, err + } + + var argoRolloutWithoutReplicas []ResourceInfo + + for _, argoRollout := range argoRolloutList.Items { + if pass, _ := filter.SetObject(&argoRollout).Run(filterOpts); pass { + continue + } + if argoRollout.Labels["kor/used"] == "false" { + reason := "Marked with unused label" + argoRolloutWithoutReplicas = append(argoRolloutWithoutReplicas, ResourceInfo{Name: argoRollout.Name, Reason: reason}) + continue + } + deploymentWorkLoadRef := argoRollout.Spec.WorkloadRef + + if deploymentWorkLoadRef.Kind == "Deployment" { + deploymentItem, _ := clientset.AppsV1().Deployments(namespace).Get(context.TODO(), deploymentWorkLoadRef.Name, metav1.GetOptions{}) + + if deploymentItem.GetName() == "" { + reason := "Rollout has no deployments" + argoRolloutWithoutReplicas = append(argoRolloutWithoutReplicas, ResourceInfo{Name: argoRollout.Name, Reason: reason}) + } + } + } + + return argoRolloutWithoutReplicas, nil +} + +func GetUnusedArgoRollouts(filterOpts *filters.Options, clientset kubernetes.Interface, clientsetargorollouts versioned.Interface, outputFormat string, opts common.Opts) (string, error) { + resources := make(map[string]map[string][]ResourceInfo) + for _, namespace := range filterOpts.Namespaces(clientset) { + diff, err := processNamespaceArgoRollouts(clientset, clientsetargorollouts, namespace, filterOpts) + + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err) + continue + } + if opts.DeleteFlag { + if diff, err = DeleteArgoRolloutsResource(diff, clientsetargorollouts, namespace, "ArgoRollout", opts.NoInteractive); err != nil { + fmt.Fprintf(os.Stderr, "Failed to delete ArgoRollout %s in namespace %s: %v\n", diff, namespace, err) + } + } + switch opts.GroupBy { + case "namespace": + resources[namespace] = make(map[string][]ResourceInfo) + resources[namespace]["ArgoRollout"] = diff + case "resource": + appendResources(resources, "ArgoRollout", namespace, diff) + } + } + + var outputBuffer bytes.Buffer + var jsonResponse []byte + switch outputFormat { + case "table": + outputBuffer = FormatOutput(resources, opts) + case "json", "yaml": + var err error + if jsonResponse, err = json.MarshalIndent(resources, "", " "); err != nil { + return "", err + } + } + + unusedDeployments, err := unusedResourceFormatter(outputFormat, outputBuffer, opts, jsonResponse) + if err != nil { + fmt.Printf("err: %v\n", err) + } + + return unusedDeployments, nil +} diff --git a/pkg/kor/argorollouts_test.go b/pkg/kor/argorollouts_test.go new file mode 100644 index 00000000..96e3bad4 --- /dev/null +++ b/pkg/kor/argorollouts_test.go @@ -0,0 +1,87 @@ +package kor + +import ( + "context" + "encoding/json" + "reflect" + "testing" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/kubernetes/scheme" + + "github.com/yonahd/kor/pkg/common" + "github.com/yonahd/kor/pkg/filters" +) + +func TestGetUnusedArgoRolloutsStructured(t *testing.T) { + clientset := fake.NewSimpleClientset() + clientsetargorollouts := createClientSetTestArgoRollouts(t) + + opts := common.Opts{ + WebhookURL: "", + Channel: "", + Token: "", + DeleteFlag: false, + NoInteractive: true, + GroupBy: "namespace", + } + + _, err := clientset.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ + ObjectMeta: v1.ObjectMeta{Name: testNamespace}, + }, v1.CreateOptions{}) + + if err != nil { + t.Fatalf("Error creating namespace %s: %v", testNamespace, err) + } + + deploymentName := "test-deployment1" + deployment1 := CreateTestDeployment(testNamespace, deploymentName, 0, AppLabels) + _, err = clientset.AppsV1().Deployments(testNamespace).Create(context.TODO(), deployment1, v1.CreateOptions{}) + + if err != nil { + t.Fatalf("Error creating fake deployment: %v", err) + } + + rollout1 := CreateTestArgoRolloutWithDeployment(testNamespace, "test-rollout", deployment1, AppLabels) + _, err = clientsetargorollouts.ArgoprojV1alpha1().Rollouts(testNamespace).Create(context.TODO(), rollout1, v1.CreateOptions{}) + if err != nil { + t.Fatalf("Error creating fake argo rollout: %v", err) + } + + err = clientset.AppsV1().Deployments(testNamespace).Delete(context.TODO(), deploymentName, v1.DeleteOptions{}) + if err != nil { + t.Fatalf("Error creating fake argo rollout: %v", err) + } + + output, err := GetUnusedArgoRollouts(&filters.Options{}, clientset, clientsetargorollouts, "json", opts) + + if err != nil { + t.Fatalf("Error calling GetUnusedArgoRolloutsStructured: %v", err) + } + + expectedOutput := map[string]map[string][]string{ + testNamespace: { + "ArgoRollout": { + "test-rollout", + }, + }, + } + + var actualOutput map[string]map[string][]string + if err := json.Unmarshal([]byte(output), &actualOutput); err != nil { + t.Fatalf("Error unmarshaling actual output: %v", err) + } + + if !reflect.DeepEqual(expectedOutput, actualOutput) { + t.Errorf("Expected output does not match actual output") + } +} + +func init() { + scheme.Scheme = runtime.NewScheme() + _ = appsv1.AddToScheme(scheme.Scheme) +} diff --git a/pkg/kor/create_test_resources.go b/pkg/kor/create_test_resources.go index bfcd22f0..d99d79da 100644 --- a/pkg/kor/create_test_resources.go +++ b/pkg/kor/create_test_resources.go @@ -1,6 +1,7 @@ package kor import ( + argorollouts "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" appsv1 "k8s.io/api/apps/v1" autoscalingv2 "k8s.io/api/autoscaling/v2" batchv1 "k8s.io/api/batch/v1" @@ -421,3 +422,20 @@ func CreateTestNetworkPolicy(name, namespace string, labels map[string]string, p }, } } + +func CreateTestArgoRolloutWithDeployment(namespace, name string, deplomentWorkLoadRef *appsv1.Deployment, labels map[string]string) *argorollouts.Rollout { + return &argorollouts.Rollout{ + ObjectMeta: v1.ObjectMeta{ + Namespace: namespace, + Name: name, + Labels: labels, + }, + Spec: argorollouts.RolloutSpec{ + WorkloadRef: &argorollouts.ObjectRef{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: deplomentWorkLoadRef.GetName(), + }, + }, + } +} diff --git a/pkg/kor/delete.go b/pkg/kor/delete.go index 7efcec3e..9e07c449 100644 --- a/pkg/kor/delete.go +++ b/pkg/kor/delete.go @@ -7,6 +7,8 @@ import ( "reflect" "strings" + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned" appsv1 "k8s.io/api/apps/v1" autoscalingv1 "k8s.io/api/autoscaling/v1" batchv1 "k8s.io/api/batch/v1" @@ -81,8 +83,15 @@ func DeleteResourceCmd() map[string]func(clientset kubernetes.Interface, namespa "NetworkPolicy": func(clientset kubernetes.Interface, namespace, name string) error { return clientset.NetworkingV1().NetworkPolicies(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) }, - "RoleBinding": func(clientset kubernetes.Interface, namespace, name string) error { - return clientset.RbacV1().RoleBindings(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) + } + + return deleteResourceApiMap +} + +func DeleteArgoRolloutsResourceCmd() map[string]func(clientset versioned.Interface, namespace, name string) error { + var deleteResourceApiMap = map[string]func(clientset versioned.Interface, namespace, name string) error{ + "ArgoRollout": func(clientset versioned.Interface, namespace, name string) error { + return clientset.ArgoprojV1alpha1().Rollouts(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) }, } @@ -133,6 +142,28 @@ func FlagResource(clientset kubernetes.Interface, namespace, resourceType, resou return err } +func FlagArgoRolloutsResource(clientset versioned.Interface, namespace, resourceType, resourceName string) error { + resource, err := getArgoRolloutsResource(clientset, namespace, resourceType, resourceName) + if err != nil { + return err + } + + labelField := reflect.ValueOf(resource).Elem().FieldByName("Labels") + if labelField.IsValid() { + labels := labelField.Interface().(map[string]string) + if labels == nil { + labels = make(map[string]string) + } + labels["kor/used"] = "true" + labelField.Set(reflect.ValueOf(labels)) + } else { + return fmt.Errorf("unable to set labels for resource type: %s", resourceType) + } + + _, err = updateArgoRolloutsResource(clientset, namespace, resourceType, resource) + return err +} + func updateResource(clientset kubernetes.Interface, namespace, resourceType string, resource interface{}) (interface{}, error) { switch resourceType { case "ConfigMap": @@ -176,6 +207,15 @@ func updateResource(clientset kubernetes.Interface, namespace, resourceType stri case "RoleBinding": return clientset.RbacV1().RoleBindings(namespace).Update(context.TODO(), resource.(*rbacv1.RoleBinding), metav1.UpdateOptions{}) } + + return nil, fmt.Errorf("resource type '%s' is not supported", resourceType) +} + +func updateArgoRolloutsResource(clientset versioned.Interface, namespace, resourceType string, resource interface{}) (interface{}, error) { + switch resourceType { + case "ArgoRollout": + return clientset.ArgoprojV1alpha1().Rollouts(namespace).Update(context.TODO(), resource.(*v1alpha1.Rollout), metav1.UpdateOptions{}) + } return nil, fmt.Errorf("resource type '%s' is not supported", resourceType) } @@ -225,6 +265,14 @@ func getResource(clientset kubernetes.Interface, namespace, resourceType, resour return nil, fmt.Errorf("resource type '%s' is not supported", resourceType) } +func getArgoRolloutsResource(clientset versioned.Interface, namespace, resourceType, resourceName string) (interface{}, error) { + switch resourceType { + case "ArgoRollout": + return clientset.ArgoprojV1alpha1().Rollouts(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{}) + } + return nil, fmt.Errorf("resource type '%s' is not supported", resourceType) +} + func DeleteResourceWithFinalizer(resources []ResourceInfo, dynamicClient dynamic.Interface, namespace string, gvr schema.GroupVersionResource, noInteractive bool) ([]ResourceInfo, error) { var remainingResources []ResourceInfo for _, resource := range resources { @@ -329,3 +377,56 @@ func DeleteResource(diff []ResourceInfo, clientset kubernetes.Interface, namespa return deletedDiff, nil } + +func DeleteArgoRolloutsResource(diff []ResourceInfo, clientset versioned.Interface, namespace, resourceType string, noInteractive bool) ([]ResourceInfo, error) { + deletedDiff := []ResourceInfo{} + + for _, resource := range diff { + deleteFunc, exists := DeleteArgoRolloutsResourceCmd()[resourceType] + if !exists { + fmt.Printf("Resource type '%s' is not supported\n", resource.Name) + continue + } + + if !noInteractive { + fmt.Printf("Do you want to delete %s %s in namespace %s? (Y/N): ", resourceType, resource.Name, namespace) + var confirmation string + _, err := fmt.Scanf("%s\n", &confirmation) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to read input: %v\n", err) + continue + } + + if strings.ToLower(confirmation) != "y" && strings.ToLower(confirmation) != "yes" { + deletedDiff = append(deletedDiff, resource) + + fmt.Printf("Do you want flag the resource %s %s in namespace %s as In Use? (Y/N): ", resourceType, resource.Name, namespace) + var inUse string + _, err := fmt.Scanf("%s\n", &inUse) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to read input: %v\n", err) + continue + } + + if strings.ToLower(inUse) == "y" || strings.ToLower(inUse) == "yes" { + if err := FlagArgoRolloutsResource(clientset, namespace, resourceType, resource.Name); err != nil { + fmt.Fprintf(os.Stderr, "Failed to flag resource %s %s in namespace %s as In Use: %v\n", resourceType, resource.Name, namespace, err) + } + continue + } + continue + } + } + + fmt.Printf("Deleting %s %s in namespace %s\n", resourceType, resource.Name, namespace) + if err := deleteFunc(clientset, namespace, resource.Name); err != nil { + fmt.Fprintf(os.Stderr, "Failed to delete %s %s in namespace %s: %v\n", resourceType, resource.Name, namespace, err) + continue + } + deletedResource := resource + deletedResource.Name += "-DELETED" + deletedDiff = append(deletedDiff, deletedResource) + } + + return deletedDiff, nil +} diff --git a/pkg/kor/deployments.go b/pkg/kor/deployments.go index efc7c05d..d700f5c8 100644 --- a/pkg/kor/deployments.go +++ b/pkg/kor/deployments.go @@ -7,6 +7,7 @@ import ( "fmt" "os" + "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -14,7 +15,7 @@ import ( "github.com/yonahd/kor/pkg/filters" ) -func processNamespaceDeployments(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) { +func processNamespaceDeployments(clientset kubernetes.Interface, clientsetrollout versioned.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) { deploymentsList, err := clientset.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels}) if err != nil { return nil, err @@ -29,23 +30,30 @@ func processNamespaceDeployments(clientset kubernetes.Interface, namespace strin if deployment.Labels["kor/used"] == "false" { reason := "Marked with unused label" - deploymentsWithoutReplicas = append(deploymentsWithoutReplicas, ResourceInfo{Name: deployment.Name, Reason: reason}) - continue + + rolloutWithSameNameDeployment, _ := RetrieveArgoRolloutsWithDeploymentWithSameName(clientsetrollout, deployment.Name, namespace) + if rolloutWithSameNameDeployment == "" { + deploymentsWithoutReplicas = append(deploymentsWithoutReplicas, ResourceInfo{Name: deployment.Name, Reason: reason}) + continue + } } if *deployment.Spec.Replicas == 0 { - reason := "Deployment has no replicas" - deploymentsWithoutReplicas = append(deploymentsWithoutReplicas, ResourceInfo{Name: deployment.Name, Reason: reason}) + rolloutWithSameNameDeployment, _ := RetrieveArgoRolloutsWithDeploymentWithSameName(clientsetrollout, deployment.Name, namespace) + if rolloutWithSameNameDeployment == "" { + reason := "Deployment has no replicas" + deploymentsWithoutReplicas = append(deploymentsWithoutReplicas, ResourceInfo{Name: deployment.Name, Reason: reason}) + } } } return deploymentsWithoutReplicas, nil } -func GetUnusedDeployments(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts common.Opts) (string, error) { +func GetUnusedDeployments(filterOpts *filters.Options, clientset kubernetes.Interface, clientsetargorollouts versioned.Interface, outputFormat string, opts common.Opts) (string, error) { resources := make(map[string]map[string][]ResourceInfo) for _, namespace := range filterOpts.Namespaces(clientset) { - diff, err := processNamespaceDeployments(clientset, namespace, filterOpts) + diff, err := processNamespaceDeployments(clientset, clientsetargorollouts, namespace, filterOpts) if err != nil { fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err) continue @@ -83,3 +91,12 @@ func GetUnusedDeployments(filterOpts *filters.Options, clientset kubernetes.Inte return unusedDeployments, nil } + +func RetrieveArgoRolloutsWithDeploymentWithSameName(clientset versioned.Interface, rolloutname string, namespace string) (string, error) { + rollout, err := clientset.ArgoprojV1alpha1().Rollouts(namespace).Get(context.TODO(), rolloutname, metav1.GetOptions{}) + if err != nil { + return "", err + } + + return rollout.GetName(), nil +} diff --git a/pkg/kor/deployments_test.go b/pkg/kor/deployments_test.go index ca4c3f8d..a5a7568d 100644 --- a/pkg/kor/deployments_test.go +++ b/pkg/kor/deployments_test.go @@ -6,9 +6,10 @@ import ( "reflect" "testing" + fakeargorollouts "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned/fake" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/scheme" @@ -55,10 +56,15 @@ func createTestDeployments(t *testing.T) *fake.Clientset { return clientset } +func createClientSetTestArgoRollouts(t *testing.T) *fakeargorollouts.Clientset { + return fakeargorollouts.NewSimpleClientset() +} + func TestProcessNamespaceDeployments(t *testing.T) { clientset := createTestDeployments(t) + clientsetargorollouts := createClientSetTestArgoRollouts(t) - deploymentsWithoutReplicas, err := processNamespaceDeployments(clientset, testNamespace, &filters.Options{}) + deploymentsWithoutReplicas, err := processNamespaceDeployments(clientset, clientsetargorollouts, testNamespace, &filters.Options{}) if err != nil { t.Errorf("Expected no error, got %v", err) } @@ -74,6 +80,7 @@ func TestProcessNamespaceDeployments(t *testing.T) { func TestGetUnusedDeploymentsStructured(t *testing.T) { clientset := createTestDeployments(t) + clientsetargorollouts := createClientSetTestArgoRollouts(t) opts := common.Opts{ WebhookURL: "", @@ -84,7 +91,7 @@ func TestGetUnusedDeploymentsStructured(t *testing.T) { GroupBy: "namespace", } - output, err := GetUnusedDeployments(&filters.Options{}, clientset, "json", opts) + output, err := GetUnusedDeployments(&filters.Options{}, clientset, clientsetargorollouts, "json", opts) if err != nil { t.Fatalf("Error calling GetUnusedDeploymentsStructured: %v", err) } @@ -108,6 +115,57 @@ func TestGetUnusedDeploymentsStructured(t *testing.T) { } } +func TestGetUnusedDeploymentsWithArgoRolloutStructured(t *testing.T) { + clientset := fake.NewSimpleClientset() + + opts := common.Opts{ + WebhookURL: "", + Channel: "", + Token: "", + DeleteFlag: false, + NoInteractive: true, + GroupBy: "namespace", + } + + _, err := clientset.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ + ObjectMeta: v1.ObjectMeta{Name: testNamespace}, + }, v1.CreateOptions{}) + + if err != nil { + t.Fatalf("Error creating namespace %s: %v", testNamespace, err) + } + deploymentName := "test-deployment1" + deplomentWorkLoadRefNoReplicas := CreateTestDeployment(testNamespace, deploymentName, 0, AppLabels) + _, err = clientset.AppsV1().Deployments(testNamespace).Create(context.TODO(), deplomentWorkLoadRefNoReplicas, v1.CreateOptions{}) + if err != nil { + t.Fatalf("Error creating fake deployment: %v", err) + } + + clientsetargorollouts := createClientSetTestArgoRollouts(t) + CreateTestArgoRolloutWithDeployment(testNamespace, deploymentName, deplomentWorkLoadRefNoReplicas, AppLabels) + + output, err := GetUnusedDeployments(&filters.Options{}, clientset, clientsetargorollouts, "json", opts) + if err != nil { + t.Fatalf("Error calling GetUnusedDeploymentsStructured: %v", err) + } + + expectedOutput := map[string]map[string][]string{ + testNamespace: { + "Deployment": { + deploymentName, + }, + }, + } + + var actualOutput map[string]map[string][]string + if err := json.Unmarshal([]byte(output), &actualOutput); err != nil { + t.Fatalf("Error unmarshaling actual output: %v", err) + } + if !reflect.DeepEqual(expectedOutput, actualOutput) { + t.Errorf("Expected output does not match actual output") + } +} + func init() { scheme.Scheme = runtime.NewScheme() _ = appsv1.AddToScheme(scheme.Scheme) diff --git a/pkg/kor/exceptions/rolebindings/rolebindings.json b/pkg/kor/exceptions/rolebindings/rolebindings.json index df1b8619..60aaead4 100644 --- a/pkg/kor/exceptions/rolebindings/rolebindings.json +++ b/pkg/kor/exceptions/rolebindings/rolebindings.json @@ -31,4 +31,4 @@ "MatchRegex": true } ] -} +} \ No newline at end of file diff --git a/pkg/kor/exporter.go b/pkg/kor/exporter.go index 39b66c3b..fb6b44db 100644 --- a/pkg/kor/exporter.go +++ b/pkg/kor/exporter.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" @@ -34,16 +35,16 @@ func init() { } // TODO: add option to change port / url !? -func Exporter(filterOptions *filters.Options, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts common.Opts, resourceList []string) { +func Exporter(filterOptions *filters.Options, clientset kubernetes.Interface, clientsetargorollouts versioned.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts common.Opts, resourceList []string) { http.Handle("/metrics", promhttp.Handler()) fmt.Println("Server listening on :8080") - go exportMetrics(filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts, resourceList) // Start exporting metrics in the background + go exportMetrics(filterOptions, clientset, clientsetargorollouts, apiExtClient, dynamicClient, outputFormat, opts, resourceList) // Start exporting metrics in the background if err := http.ListenAndServe(":8080", nil); err != nil { fmt.Println(err) } } -func exportMetrics(filterOptions *filters.Options, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts common.Opts, resourceList []string) { +func exportMetrics(filterOptions *filters.Options, clientset kubernetes.Interface, clientsetargorollouts versioned.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts common.Opts, resourceList []string) { exporterInterval := os.Getenv("EXPORTER_INTERVAL") if exporterInterval == "" { exporterInterval = "10" @@ -56,7 +57,7 @@ func exportMetrics(filterOptions *filters.Options, clientset kubernetes.Interfac for { fmt.Println("collecting unused resources") - if korOutput, err := getUnusedResources(filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts, resourceList); err != nil { + if korOutput, err := getUnusedResources(filterOptions, clientset, clientsetargorollouts, apiExtClient, dynamicClient, outputFormat, opts, resourceList); err != nil { fmt.Println(err) os.Exit(1) } else { @@ -80,10 +81,10 @@ func exportMetrics(filterOptions *filters.Options, clientset kubernetes.Interfac } } -func getUnusedResources(filterOptions *filters.Options, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts common.Opts, resourceList []string) (string, error) { +func getUnusedResources(filterOptions *filters.Options, clientset kubernetes.Interface, clientsetargorollouts versioned.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts common.Opts, resourceList []string) (string, error) { if len(resourceList) == 0 || (len(resourceList) == 1 && resourceList[0] == "all") { - return GetUnusedAll(filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts) + return GetUnusedAll(filterOptions, clientset, clientsetargorollouts, apiExtClient, dynamicClient, outputFormat, opts) } - return GetUnusedMulti(strings.Join(resourceList, ","), filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts) + return GetUnusedMulti(strings.Join(resourceList, ","), filterOptions, clientset, clientsetargorollouts, apiExtClient, dynamicClient, outputFormat, opts) } diff --git a/pkg/kor/kor.go b/pkg/kor/kor.go index be6968c0..7a85f505 100644 --- a/pkg/kor/kor.go +++ b/pkg/kor/kor.go @@ -8,6 +8,7 @@ import ( "regexp" "sort" + clientargorollouts "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned" apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" @@ -91,6 +92,21 @@ func GetKubeClient(kubeconfig string) *kubernetes.Clientset { return clientset } +func GetKubeClientArgoRollouts(kubeconfig string) *clientargorollouts.Clientset { + config, err := GetConfig(kubeconfig) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to load kubeconfig: %v\n", err) + os.Exit(1) + } + + clientset, err := clientargorollouts.NewForConfig(config) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create Kubernetes client for argo rollouts: %v\n", err) + os.Exit(1) + } + return clientset +} + func GetAPIExtensionsClient(kubeconfig string) *apiextensionsclientset.Clientset { config, err := GetConfig(kubeconfig) if err != nil { diff --git a/pkg/kor/multi.go b/pkg/kor/multi.go index ef2dfd94..fed71724 100644 --- a/pkg/kor/multi.go +++ b/pkg/kor/multi.go @@ -7,6 +7,7 @@ import ( "os" "strings" + "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned" apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" @@ -52,7 +53,7 @@ func retrieveNoNamespaceDiff(clientset kubernetes.Interface, apiExtClient apiext return noNamespaceDiff, clearedResourceList } -func retrieveNamespaceDiffs(clientset kubernetes.Interface, namespace string, resourceList []string, filterOpts *filters.Options) []ResourceDiff { +func retrieveNamespaceDiffs(clientset kubernetes.Interface, clientsetargorollouts versioned.Interface, namespace string, resourceList []string, filterOpts *filters.Options) []ResourceDiff { var allDiffs []ResourceDiff for _, resource := range resourceList { var diffResult ResourceDiff @@ -66,7 +67,7 @@ func retrieveNamespaceDiffs(clientset kubernetes.Interface, namespace string, re case "sa", "serviceaccount", "serviceaccounts": diffResult = getUnusedServiceAccounts(clientset, namespace, filterOpts) case "deploy", "deployment", "deployments": - diffResult = getUnusedDeployments(clientset, namespace, filterOpts) + diffResult = getUnusedDeployments(clientset, clientsetargorollouts, namespace, filterOpts) case "sts", "statefulset", "statefulsets": diffResult = getUnusedStatefulSets(clientset, namespace, filterOpts) case "role", "roles": @@ -91,6 +92,8 @@ func retrieveNamespaceDiffs(clientset kubernetes.Interface, namespace string, re diffResult = getUnusedNetworkPolicies(clientset, namespace, filterOpts) case "rolebinding", "rolebindings": diffResult = getUnusedNetworkPolicies(clientset, namespace, filterOpts) + case "argorollouts": + diffResult = getUnusedArgoRollouts(clientset, clientsetargorollouts, namespace, filterOpts) default: fmt.Printf("resource type %q is not supported\n", resource) } @@ -99,7 +102,7 @@ func retrieveNamespaceDiffs(clientset kubernetes.Interface, namespace string, re return allDiffs } -func GetUnusedMulti(resourceNames string, filterOpts *filters.Options, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts common.Opts) (string, error) { +func GetUnusedMulti(resourceNames string, filterOpts *filters.Options, clientset kubernetes.Interface, clientsetargorollouts versioned.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts common.Opts) (string, error) { resourceList := strings.Split(resourceNames, ",") namespaces := filterOpts.Namespaces(clientset) resources := make(map[string]map[string][]ResourceInfo) @@ -117,6 +120,9 @@ func GetUnusedMulti(resourceNames string, filterOpts *filters.Options, clientset if diff.diff, err = DeleteResource(diff.diff, clientset, "", diff.resourceType, opts.NoInteractive); err != nil { fmt.Fprintf(os.Stderr, "Failed to delete %s %s: %v\n", diff.resourceType, diff.diff, err) } + if diff.diff, err = DeleteArgoRolloutsResource(diff.diff, clientsetargorollouts, "", diff.resourceType, opts.NoInteractive); err != nil { + fmt.Fprintf(os.Stderr, "Failed to delete %s %s: %v\n", diff.resourceType, diff.diff, err) + } } switch opts.GroupBy { case "namespace": @@ -129,16 +135,19 @@ func GetUnusedMulti(resourceNames string, filterOpts *filters.Options, clientset } for _, namespace := range namespaces { - allDiffs := retrieveNamespaceDiffs(clientset, namespace, resourceList, filterOpts) + allDiffs := retrieveNamespaceDiffs(clientset, clientsetargorollouts, namespace, resourceList, filterOpts) if opts.GroupBy == "namespace" { resources[namespace] = make(map[string][]ResourceInfo) } - for _, diff := range allDiffs { if opts.DeleteFlag { if diff.diff, err = DeleteResource(diff.diff, clientset, namespace, diff.resourceType, opts.NoInteractive); err != nil { fmt.Fprintf(os.Stderr, "Failed to delete %s %s in namespace %s: %v\n", diff.resourceType, diff.diff, namespace, err) } + + if diff.diff, err = DeleteArgoRolloutsResource(diff.diff, clientsetargorollouts, namespace, diff.resourceType, opts.NoInteractive); err != nil { + fmt.Fprintf(os.Stderr, "Failed to delete %s %s: %v\n", diff.resourceType, diff.diff, err) + } } switch opts.GroupBy { case "namespace": diff --git a/pkg/kor/multi_test.go b/pkg/kor/multi_test.go index 3c4828f2..611a6ec4 100644 --- a/pkg/kor/multi_test.go +++ b/pkg/kor/multi_test.go @@ -43,10 +43,11 @@ func createTestMultiResources(t *testing.T) *fake.Clientset { func TestRetrieveNamespaceDiff(t *testing.T) { clientset := createTestMultiResources(t) + clientsetargorollouts := createClientSetTestArgoRollouts(t) resourceList := []string{"cm", "pdb", "deployment"} filterOpts := &filters.Options{} - namespaceDiff := retrieveNamespaceDiffs(clientset, testNamespace, resourceList, filterOpts) + namespaceDiff := retrieveNamespaceDiffs(clientset, clientsetargorollouts, testNamespace, resourceList, filterOpts) if len(namespaceDiff) != 3 { t.Fatalf("Expected 3 diffs, got %d", len(namespaceDiff)) @@ -68,6 +69,7 @@ func TestRetrieveNamespaceDiff(t *testing.T) { func TestGetUnusedMulti(t *testing.T) { clientset := createTestMultiResources(t) + clientsetargorollouts := createClientSetTestArgoRollouts(t) resourceList := "cm,pdb,deployment" opts := common.Opts{ @@ -79,7 +81,7 @@ func TestGetUnusedMulti(t *testing.T) { GroupBy: "namespace", } - output, err := GetUnusedMulti(resourceList, &filters.Options{}, clientset, nil, nil, "json", opts) + output, err := GetUnusedMulti(resourceList, &filters.Options{}, clientset, clientsetargorollouts, nil, nil, "json", opts) if err != nil { t.Fatalf("Error calling GetUnusedMulti: %v", err)