Repository: yonahd/kor
Pull request #278 (2024-05-21 11:13:28)

Feat: show unused reason
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for additional information on contributing to this repository!

<!--
  !!!! README !!!! Please fill this out.

  Please follow conventional commit naming conventions:

  https://www.conventionalcommits.org/en/v1.0.0/#summary
-->

<!-- A short description of what your PR does and what it solves. -->

## What this PR does / why we need it

## PR Checklist

- [ ] This PR adds K8s exceptions (false positives)
- [x] This PR adds new code
- [x] This PR includes test for any new code

## Github Issue

[#167 ]





Patch:
diff --git a/README.md b/README.md
index 18b5592..941bc52 100644
--- a/README.md
+++ b/README.md
@@ -134,6 +134,7 @@ Kor provides various subcommands to identify and list unused resources. The avai
       --no-interactive               Do not prompt for confirmation when deleting resources. Be careful using this flag!
       --older-than string            The minimum age of the resources to be considered unused. This flag cannot be used together with newer-than flag. Example: --older-than=1h2m
   -o, --output string                Output format (table, json or yaml) (default "table")
+  -r, --show-reason                  Print reason resource is considered unused
       --slack-auth-token string      Slack auth token to send notifications to. --slack-auth-token requires --slack-channel to be set.
       --slack-channel string         Slack channel to send notifications to. --slack-channel requires --slack-auth-token to be set.
       --slack-webhook-url string     Slack webhook URL to send notifications to
diff --git a/cmd/kor/root.go b/cmd/kor/root.go
index bcc2c98..ef06101 100644
--- a/cmd/kor/root.go
+++ b/cmd/kor/root.go
@@ -60,6 +60,7 @@ func init() {
 	rootCmd.PersistentFlags().BoolVar(&opts.NoInteractive, "no-interactive", false, "Do not prompt for confirmation when deleting resources. Be careful using this flag!")
 	rootCmd.PersistentFlags().BoolVarP(&opts.Verbose, "verbose", "v", false, "Verbose output (print empty namespaces)")
 	rootCmd.PersistentFlags().StringVar(&opts.GroupBy, "group-by", "namespace", "Group output by (namespace, resource)")
+	rootCmd.PersistentFlags().BoolVarP(&opts.ShowReason, "show-reason", "r", false, "Print reason resource is considered unused")
 	addFilterOptionsFlag(rootCmd, filterOptions)
 }
 
diff --git a/pkg/kor/all.go b/pkg/kor/all.go
index 8307105..5880905 100644
--- a/pkg/kor/all.go
+++ b/pkg/kor/all.go
@@ -20,7 +20,7 @@ type GetUnusedResourceJSONResponse struct {
 
 type ResourceDiff struct {
 	resourceType string
-	diff         []string
+	diff         []ResourceInfo
 }
 
 func getUnusedCMs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff {
@@ -252,11 +252,11 @@ func getUnusedStorageClasses(clientset kubernetes.Interface, filterOpts *filters
 }
 
 func GetUnusedAllNamespaced(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
+			resources[namespace] = make(map[string][]ResourceInfo)
 			resources[namespace]["ConfigMap"] = getUnusedCMs(clientset, namespace, filterOpts).diff
 			resources[namespace]["Service"] = getUnusedSVCs(clientset, namespace, filterOpts).diff
 			resources[namespace]["Secret"] = getUnusedSecrets(clientset, namespace, filterOpts).diff
@@ -312,10 +312,10 @@ func GetUnusedAllNamespaced(filterOpts *filters.Options, clientset kubernetes.In
 }
 
 func GetUnusedAllNonNamespaced(filterOpts *filters.Options, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	switch opts.GroupBy {
 	case "namespace":
-		resources[""] = make(map[string][]string)
+		resources[""] = make(map[string][]ResourceInfo)
 		resources[""]["Crd"] = getUnusedCrds(apiExtClient, dynamicClient, filterOpts).diff
 		resources[""]["Pv"] = getUnusedPvs(clientset, filterOpts).diff
 		resources[""]["ClusterRole"] = getUnusedClusterRoles(clientset, filterOpts).diff
diff --git a/pkg/kor/clusterroles.go b/pkg/kor/clusterroles.go
index 3b324ef..3577d35 100644
--- a/pkg/kor/clusterroles.go
+++ b/pkg/kor/clusterroles.go
@@ -156,7 +156,7 @@ func retrieveClusterRoleNames(clientset kubernetes.Interface, filterOpts *filter
 	return names, unusedClusterRoles, nil
 }
 
-func processClusterRoles(clientset kubernetes.Interface, filterOpts *filters.Options) ([]string, error) {
+func processClusterRoles(clientset kubernetes.Interface, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	usedClusterRoles, err := retrieveUsedClusterRoles(clientset, filterOpts)
 	if err != nil {
 		return nil, err
@@ -169,28 +169,37 @@ func processClusterRoles(clientset kubernetes.Interface, filterOpts *filters.Opt
 		return nil, err
 	}
 
-	diff := CalculateResourceDifference(usedClusterRoles, clusterRoleNames)
-	diff = append(diff, unusedClusterRoles...)
+	var diff []ResourceInfo
+
+	for _, name := range CalculateResourceDifference(usedClusterRoles, clusterRoleNames) {
+		reason := "ClusterRole is not used by any RoleBinding or ClusterRoleBinding"
+		diff = append(diff, ResourceInfo{Name: name, Reason: reason})
+	}
+
+	for _, name := range unusedClusterRoles {
+		reason := "Marked with unused label"
+		diff = append(diff, ResourceInfo{Name: name, Reason: reason})
+	}
 
 	return diff, nil
 
 }
 
 func GetUnusedClusterRoles(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	diff, err := processClusterRoles(clientset, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to process cluster role : %v\n", err)
 	}
 	switch opts.GroupBy {
 	case "namespace":
-		resources[""] = make(map[string][]string)
+		resources[""] = make(map[string][]ResourceInfo)
 		resources[""]["ClusterRole"] = diff
 	case "resource":
 		appendResources(resources, "ClusterRole", "", diff)
 	}
 	if opts.DeleteFlag {
-		if diff, err = DeleteResource(diff, clientset, "", "ClusterRole", opts.NoInteractive); err != nil {
+		if diff, err = DeleteResource2(diff, clientset, "", "ClusterRole", opts.NoInteractive); err != nil {
 			fmt.Fprintf(os.Stderr, "Failed to delete clusterRole %s : %v\n", diff, err)
 		}
 	}
diff --git a/pkg/kor/configmaps.go b/pkg/kor/configmaps.go
index 5d95d0a..d5a8088 100644
--- a/pkg/kor/configmaps.go
+++ b/pkg/kor/configmaps.go
@@ -101,7 +101,7 @@ func retrieveConfigMapNames(clientset kubernetes.Interface, namespace string, fi
 	return names, unusedConfigmapNames, nil
 }
 
-func processNamespaceCM(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+func processNamespaceCM(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	volumesCM, envCM, envFromCM, envFromContainerCM, envFromInitContainerCM, err := retrieveUsedCM(clientset, namespace)
 	if err != nil {
 		return nil, err
@@ -135,12 +135,10 @@ func processNamespaceCM(clientset kubernetes.Interface, namespace string, filter
 		usedConfigMaps = append(usedConfigMaps, slice...)
 	}
 
-	diff := CalculateResourceDifference(usedConfigMaps, configMapNames)
-	diff = append(diff, unusedConfigmapNames...)
+	var diff []ResourceInfo
 
-	var result []string
-	for _, cmName := range diff {
-		exceptionFound, err := isResourceException(cmName, namespace, config.ExceptionConfigMaps)
+	for _, name := range CalculateResourceDifference(usedConfigMaps, configMapNames) {
+		exceptionFound, err := isResourceException(name, namespace, config.ExceptionConfigMaps)
 		if err != nil {
 			return nil, err
 		}
@@ -148,14 +146,20 @@ func processNamespaceCM(clientset kubernetes.Interface, namespace string, filter
 		if exceptionFound {
 			continue
 		}
-		result = append(result, cmName)
+		reason := "ConfigMap is not used in any pod or container"
+		diff = append(diff, ResourceInfo{Name: name, Reason: reason})
 	}
 
-	return result, nil
+	for _, name := range unusedConfigmapNames {
+		reason := "Marked with unused label"
+		diff = append(diff, ResourceInfo{Name: name, Reason: reason})
+	}
+
+	return diff, nil
 }
 
 func GetUnusedConfigmaps(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespaceCM(clientset, namespace, filterOpts)
 		if err != nil {
@@ -164,13 +168,13 @@ func GetUnusedConfigmaps(filterOpts *filters.Options, clientset kubernetes.Inter
 		}
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
+			resources[namespace] = make(map[string][]ResourceInfo)
 			resources[namespace]["ConfigMap"] = diff
 		case "resource":
 			appendResources(resources, "ConfigMap", namespace, diff)
 		}
 		if opts.DeleteFlag {
-			if diff, err = DeleteResource(diff, clientset, namespace, "ConfigMap", opts.NoInteractive); err != nil {
+			if diff, err = DeleteResource2(diff, clientset, namespace, "ConfigMap", opts.NoInteractive); err != nil {
 				fmt.Fprintf(os.Stderr, "Failed to delete ConfigMap %s in namespace %s: %v\n", diff, namespace, err)
 			}
 		}
diff --git a/pkg/kor/crds.go b/pkg/kor/crds.go
index f04eecb..e9c0ced 100644
--- a/pkg/kor/crds.go
+++ b/pkg/kor/crds.go
@@ -20,9 +20,9 @@ import (
 //go:embed exceptions/crds/crds.json
 var crdsConfig []byte
 
-func processCrds(apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, filterOpts *filters.Options) ([]string, error) {
+func processCrds(apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, filterOpts *filters.Options) ([]ResourceInfo, error) {
 
-	var unusedCRDs []string
+	var unusedCRDs []ResourceInfo
 
 	crds, err := apiExtClient.ApiextensionsV1().CustomResourceDefinitions().List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
@@ -58,21 +58,22 @@ func processCrds(apiExtClient apiextensionsclientset.Interface, dynamicClient dy
 			return nil, err
 		}
 		if len(instances.Items) == 0 {
-			unusedCRDs = append(unusedCRDs, crd.Name)
+			reason := "CRD has no instances"
+			unusedCRDs = append(unusedCRDs, ResourceInfo{Name: crd.Name, Reason: reason})
 		}
 	}
 	return unusedCRDs, nil
 }
 
 func GetUnusedCrds(_ *filters.Options, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	diff, err := processCrds(apiExtClient, dynamicClient, &filters.Options{})
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to process crds: %v\n", err)
 	}
 	switch opts.GroupBy {
 	case "namespace":
-		resources[""] = make(map[string][]string)
+		resources[""] = make(map[string][]ResourceInfo)
 		resources[""]["Crd"] = diff
 	case "resource":
 		appendResources(resources, "Crd", "", diff)
diff --git a/pkg/kor/daemonsets.go b/pkg/kor/daemonsets.go
index 4ca8d3a..2669101 100644
--- a/pkg/kor/daemonsets.go
+++ b/pkg/kor/daemonsets.go
@@ -17,13 +17,13 @@ import (
 //go:embed exceptions/daemonsets/daemonsets.json
 var daemonsetsConfig []byte
 
-func processNamespaceDaemonSets(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+func processNamespaceDaemonSets(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	daemonSetsList, err := clientset.AppsV1().DaemonSets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
 
-	var daemonSetsWithoutReplicas []string
+	var daemonSetsWithoutReplicas []ResourceInfo
 
 	for _, daemonSet := range daemonSetsList.Items {
 		if pass, _ := filter.SetObject(&daemonSet).Run(filterOpts); pass {
@@ -45,12 +45,14 @@ func processNamespaceDaemonSets(clientset kubernetes.Interface, namespace string
 		}
 
 		if daemonSet.Labels["kor/used"] == "false" {
-			daemonSetsWithoutReplicas = append(daemonSetsWithoutReplicas, daemonSet.Name)
+			reason := "Marked with unused label"
+			daemonSetsWithoutReplicas = append(daemonSetsWithoutReplicas, ResourceInfo{Name: daemonSet.Name, Reason: reason})
 			continue
 		}
 
 		if daemonSet.Status.CurrentNumberScheduled == 0 {
-			daemonSetsWithoutReplicas = append(daemonSetsWithoutReplicas, daemonSet.Name)
+			reason := "DaemonSet has no replicas"
+			daemonSetsWithoutReplicas = append(daemonSetsWithoutReplicas, ResourceInfo{Name: daemonSet.Name, Reason: reason})
 		}
 	}
 
@@ -58,7 +60,7 @@ func processNamespaceDaemonSets(clientset kubernetes.Interface, namespace string
 }
 
 func GetUnusedDaemonSets(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespaceDaemonSets(clientset, namespace, filterOpts)
 		if err != nil {
@@ -67,13 +69,13 @@ func GetUnusedDaemonSets(filterOpts *filters.Options, clientset kubernetes.Inter
 		}
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
+			resources[namespace] = make(map[string][]ResourceInfo)
 			resources[namespace]["DaemonSet"] = diff
 		case "resource":
 			appendResources(resources, "DaemonSet", namespace, diff)
 		}
 		if opts.DeleteFlag {
-			if diff, err = DeleteResource(diff, clientset, namespace, "DaemonSet", opts.NoInteractive); err != nil {
+			if diff, err = DeleteResource2(diff, clientset, namespace, "DaemonSet", opts.NoInteractive); err != nil {
 				fmt.Fprintf(os.Stderr, "Failed to delete DaemonSet %s in namespace %s: %v\n", diff, namespace, err)
 			}
 		}
diff --git a/pkg/kor/delete.go b/pkg/kor/delete.go
index 6b80572..73444a9 100644
--- a/pkg/kor/delete.go
+++ b/pkg/kor/delete.go
@@ -211,13 +211,11 @@ func getResource(clientset kubernetes.Interface, namespace, resourceType, resour
 	return nil, fmt.Errorf("resource type '%s' is not supported", resourceType)
 }
 
-func DeleteResourceWithFinalizer(diff []string, dynamicClient dynamic.Interface, namespace string, gvr schema.GroupVersionResource, noInteractive bool) ([]string, error) {
-	deletedDiff := []string{}
-
-	for _, resourceName := range diff {
-
+func DeleteResourceWithFinalizer(resources []ResourceInfo, dynamicClient dynamic.Interface, namespace string, gvr schema.GroupVersionResource, noInteractive bool) ([]ResourceInfo, error) {
+	var remainingResources []ResourceInfo
+	for _, resource := range resources {
 		if !noInteractive {
-			fmt.Printf("Do you want to delete %s %s in namespace %s? (Y/N): ", gvr.Resource, resourceName, namespace)
+			fmt.Printf("Do you want to delete %s %s in namespace %s? (Y/N): ", gvr.Resource, resource.Name, namespace)
 			var confirmation string
 			_, err := fmt.Scanf("%s", &confirmation)
 			if err != nil {
@@ -226,40 +224,43 @@ func DeleteResourceWithFinalizer(diff []string, dynamicClient dynamic.Interface,
 			}
 
 			if strings.ToLower(confirmation) != "y" && strings.ToLower(confirmation) != "yes" {
-				deletedDiff = append(deletedDiff, resourceName)
+				resource.Reason = "not deleted - user declined"
+				remainingResources = append(remainingResources, resource)
 
-				fmt.Printf("Do you want flag the resource %s %s in namespace %s as In Use? (Y/N): ", gvr.Resource, resourceName, namespace)
+				fmt.Printf("Do you want to flag the resource %s %s in namespace %s as In Use? (Y/N): ", gvr.Resource, resource.Name, namespace)
 				var inUse string
-				_, err := fmt.Scanf("%s", &inUse)
+				_, err = fmt.Scanf("%s", &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 := FlagDynamicResource(dynamicClient, namespace, gvr, resourceName); err != nil {
-						fmt.Fprintf(os.Stderr, "Failed to flag resource %s %s in namespace %s as In Use: %v\n", gvr.Resource, resourceName, namespace, err)
+					if err := FlagDynamicResource(dynamicClient, namespace, gvr, resource.Name); err != nil {
+						fmt.Fprintf(os.Stderr, "Failed to flag resource %s %s in namespace %s as In Use: %v\n", gvr.Resource, resource.Name, namespace, err)
+					} else {
+						resource.Reason = "flagged as in use"
 					}
-					continue
 				}
 				continue
 			}
 		}
 
-		fmt.Printf("Deleting %s %s in namespace %s\n", gvr.Resource, resourceName, namespace)
+		fmt.Printf("Deleting %s %s in namespace %s\n", gvr.Resource, resource.Name, namespace)
 		if _, err := dynamicClient.
 			Resource(gvr).
 			Namespace(namespace).
-			Patch(context.TODO(), resourceName, types.MergePatchType,
+			Patch(context.TODO(), resource.Name, types.MergePatchType,
 				[]byte(`{"metadata":{"finalizers":null}}`),
 				metav1.PatchOptions{}); err != nil {
-			fmt.Fprintf(os.Stderr, "Failed to delete %s %s in namespace %s: %v\n", gvr.Resource, resourceName, namespace, err)
+			fmt.Fprintf(os.Stderr, "Failed to delete %s %s in namespace %s: %v\n", gvr.Resource, resource.Name, namespace, err)
 			continue
 		}
-		deletedDiff = append(deletedDiff, resourceName+"-DELETED")
+		resource.Name = resource.Name + "-DELETED"
+		remainingResources = append(remainingResources, resource)
 	}
 
-	return deletedDiff, nil
+	return remainingResources, nil
 }
 
 func DeleteResource(diff []string, clientset kubernetes.Interface, namespace, resourceType string, noInteractive bool) ([]string, error) {
@@ -312,3 +313,56 @@ func DeleteResource(diff []string, clientset kubernetes.Interface, namespace, re
 
 	return deletedDiff, nil
 }
+
+func DeleteResource2(diff []ResourceInfo, clientset kubernetes.Interface, namespace, resourceType string, noInteractive bool) ([]ResourceInfo, error) {
+	deletedDiff := []ResourceInfo{}
+
+	for _, resource := range diff {
+		deleteFunc, exists := DeleteResourceCmd()[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", &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", &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 := FlagResource(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 8135619..cc46218 100644
--- a/pkg/kor/deployments.go
+++ b/pkg/kor/deployments.go
@@ -13,13 +13,13 @@ import (
 	"github.com/yonahd/kor/pkg/filters"
 )
 
-func processNamespaceDeployments(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+func processNamespaceDeployments(clientset kubernetes.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
 	}
 
-	var deploymentsWithoutReplicas []string
+	var deploymentsWithoutReplicas []ResourceInfo
 
 	for _, deployment := range deploymentsList.Items {
 		if pass, _ := filter.SetObject(&deployment).Run(filterOpts); pass {
@@ -27,12 +27,14 @@ func processNamespaceDeployments(clientset kubernetes.Interface, namespace strin
 		}
 
 		if deployment.Labels["kor/used"] == "false" {
-			deploymentsWithoutReplicas = append(deploymentsWithoutReplicas, deployment.Name)
+			reason := "Marked with unused label"
+			deploymentsWithoutReplicas = append(deploymentsWithoutReplicas, ResourceInfo{Name: deployment.Name, Reason: reason})
 			continue
 		}
 
 		if *deployment.Spec.Replicas == 0 {
-			deploymentsWithoutReplicas = append(deploymentsWithoutReplicas, deployment.Name)
+			reason := "Deployment has no replicas"
+			deploymentsWithoutReplicas = append(deploymentsWithoutReplicas, ResourceInfo{Name: deployment.Name, Reason: reason})
 		}
 	}
 
@@ -40,7 +42,7 @@ func processNamespaceDeployments(clientset kubernetes.Interface, namespace strin
 }
 
 func GetUnusedDeployments(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespaceDeployments(clientset, namespace, filterOpts)
 		if err != nil {
@@ -49,13 +51,13 @@ func GetUnusedDeployments(filterOpts *filters.Options, clientset kubernetes.Inte
 		}
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
+			resources[namespace] = make(map[string][]ResourceInfo)
 			resources[namespace]["Deployment"] = diff
 		case "resource":
 			appendResources(resources, "Deployment", namespace, diff)
 		}
 		if opts.DeleteFlag {
-			if diff, err = DeleteResource(diff, clientset, namespace, "Deployment", opts.NoInteractive); err != nil {
+			if diff, err = DeleteResource2(diff, clientset, namespace, "Deployment", opts.NoInteractive); err != nil {
 				fmt.Fprintf(os.Stderr, "Failed to delete Deployment %s in namespace %s: %v\n", diff, namespace, err)
 			}
 		}
diff --git a/pkg/kor/finalizers.go b/pkg/kor/finalizers.go
index a01a4bf..ac6cab5 100644
--- a/pkg/kor/finalizers.go
+++ b/pkg/kor/finalizers.go
@@ -24,8 +24,8 @@ func CheckFinalizers(finalizers []string, deletionTimestamp *metav1.Time) bool {
 	return false
 }
 
-func retrievePendingDeletionResources(resourceTypes []*metav1.APIResourceList, dynamicClient dynamic.Interface, filterOpts *filters.Options) (map[string]map[schema.GroupVersionResource][]string, error) {
-	pendingDeletionResources := make(map[string]map[schema.GroupVersionResource][]string) //map[namespace]map[gvr][]resourceNames
+func retrievePendingDeletionResources(resourceTypes []*metav1.APIResourceList, dynamicClient dynamic.Interface, filterOpts *filters.Options) (map[string]map[schema.GroupVersionResource][]ResourceInfo, error) {
+	pendingDeletionResources := make(map[string]map[schema.GroupVersionResource][]ResourceInfo) //map[namespace]map[gvr][]resourceNames
 
 	for _, apiResourceList := range resourceTypes {
 		gv, err := schema.ParseGroupVersion(apiResourceList.GroupVersion)
@@ -52,9 +52,13 @@ func retrievePendingDeletionResources(resourceTypes []*metav1.APIResourceList, d
 					}
 					if CheckFinalizers(item.GetFinalizers(), item.GetDeletionTimestamp()) {
 						if pendingDeletionResources[item.GetNamespace()] == nil {
-							pendingDeletionResources[item.GetNamespace()] = make(map[schema.GroupVersionResource][]string)
+							pendingDeletionResources[item.GetNamespace()] = make(map[schema.GroupVersionResource][]ResourceInfo)
 						}
-						pendingDeletionResources[item.GetNamespace()][gvr] = append(pendingDeletionResources[item.GetNamespace()][gvr], item.GetName())
+						finalizerInfo := ResourceInfo{
+							Name:   item.GetName(),
+							Reason: "Pending deletion waiting for finalizers",
+						}
+						pendingDeletionResources[item.GetNamespace()][gvr] = append(pendingDeletionResources[item.GetNamespace()][gvr], finalizerInfo)
 					}
 				}
 			}
@@ -63,7 +67,7 @@ func retrievePendingDeletionResources(resourceTypes []*metav1.APIResourceList, d
 	return pendingDeletionResources, nil
 }
 
-func getResourcesWithFinalizersPendingDeletion(clientset kubernetes.Interface, dynamicClient dynamic.Interface, filterOpts *filters.Options) (map[string]map[schema.GroupVersionResource][]string, error) {
+func getResourcesWithFinalizersPendingDeletion(clientset kubernetes.Interface, dynamicClient dynamic.Interface, filterOpts *filters.Options) (map[string]map[schema.GroupVersionResource][]ResourceInfo, error) {
 	// Use the discovery client to fetch API resources
 	resourceTypes, err := clientset.Discovery().ServerPreferredNamespacedResources()
 	if err != nil {
@@ -77,14 +81,14 @@ func getResourcesWithFinalizersPendingDeletion(clientset kubernetes.Interface, d
 func GetUnusedfinalizers(filterOpts *filters.Options, clientset kubernetes.Interface, dynamicClient *dynamic.DynamicClient, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
 	namespaces := filterOpts.Namespaces(clientset)
-	response := make(map[string]map[string][]string)
+	response := make(map[string]map[string][]ResourceInfo)
 	pendingDeletionDiffs, err := getResourcesWithFinalizersPendingDeletion(clientset, dynamicClient, filterOpts)
 
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to process resources waiting for finalizers: %v\n", err)
 	}
 
-	allDiffs := make(map[string][]string)
+	allDiffs := make(map[string][]ResourceInfo)
 
 	for namespace, resourceType := range pendingDeletionDiffs {
 		if slices.Contains(namespaces, namespace) {
diff --git a/pkg/kor/formatter.go b/pkg/kor/formatter.go
new file mode 100644
index 0000000..fdfd432
--- /dev/null
+++ b/pkg/kor/formatter.go
@@ -0,0 +1,234 @@
+package kor
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"strings"
+
+	"github.com/olekukonko/tablewriter"
+	"sigs.k8s.io/yaml"
+)
+
+type ResourceInfo struct {
+	Name   string `json:"name"`
+	Reason string `json:"reason,omitempty"`
+}
+
+func getTableRow(index int, columns ...string) []string {
+	row := make([]string, 0, len(columns)+1)
+	row = append(row, fmt.Sprintf("%d", index+1))
+	row = append(row, columns...)
+	return row
+}
+
+func unusedResourceFormatter(outputFormat string, outputBuffer bytes.Buffer, opts Opts, jsonResponse []byte) (string, error) {
+	switch outputFormat {
+	case "table":
+		if opts.WebhookURL == "" || opts.Channel == "" || opts.Token != "" {
+			return outputBuffer.String(), nil
+		}
+		if err := SendToSlack(SlackMessage{}, opts, outputBuffer.String()); err != nil {
+			return "", fmt.Errorf("failed to send message to slack: %w", err)
+		}
+	case "json", "yaml":
+		var resources map[string]map[string][]ResourceInfo
+		if err := json.Unmarshal(jsonResponse, &resources); err != nil {
+			return "", err
+		}
+
+		if !opts.ShowReason {
+			// Create a map of namespaces with their corresponding maps of resource types and lists of resource names
+			namespaces := make(map[string]map[string][]string)
+			for namespace, resourceMap := range resources {
+				for resourceType, infoSlice := range resourceMap {
+					for _, info := range infoSlice {
+						if _, ok := namespaces[namespace]; !ok {
+							namespaces[namespace] = make(map[string][]string)
+						}
+						namespaces[namespace][resourceType] = append(namespaces[namespace][resourceType], info.Name)
+					}
+				}
+			}
+			// Marshal the map to JSON format
+			modifiedJSONResponse, err := json.MarshalIndent(namespaces, "", "  ")
+			if err != nil {
+				return "", err
+			}
+			if outputFormat == "yaml" {
+				modifiedJSONResponse, err = yaml.JSONToYAML(modifiedJSONResponse)
+				if err != nil {
+					return "", err
+				}
+			}
+			return string(modifiedJSONResponse), nil
+		}
+
+		modifiedJSONResponse, err := json.MarshalIndent(resources, "", "  ")
+		if err != nil {
+			return "", err
+		}
+		if outputFormat == "yaml" {
+			modifiedJSONResponse, err = yaml.JSONToYAML(modifiedJSONResponse)
+			if err != nil {
+				return "", err
+			}
+		}
+		return string(modifiedJSONResponse), nil
+	default:
+		return "", fmt.Errorf("unsupported output format: %s", outputFormat)
+	}
+	return "", fmt.Errorf("unsupported output format: %s", outputFormat)
+}
+
+func FormatOutput(resources map[string]map[string][]ResourceInfo, opts Opts) bytes.Buffer {
+	var output bytes.Buffer
+	switch opts.GroupBy {
+	case "namespace":
+		for namespace, diffs := range resources {
+			output.WriteString(formatOutputForNamespace(namespace, diffs, opts))
+		}
+	case "resource":
+		for resource, diffs := range resources {
+			output.WriteString(formatOutputForResource(resource, diffs, opts))
+		}
+	}
+	return output
+}
+
+func formatOutputForNamespace(namespace string, resources map[string][]ResourceInfo, opts Opts) string {
+	var buf strings.Builder
+	table := tablewriter.NewWriter(&buf)
+	table.SetColWidth(60)
+	table.SetHeader(getTableHeader(opts.GroupBy, opts.ShowReason))
+	allEmpty := true
+	var index int
+	for resourceType, diff := range resources {
+		for _, info := range diff {
+			row := getTableRow(index, resourceType, info.Name)
+			if opts.ShowReason && info.Reason != "" {
+				row = append(row, info.Reason)
+			}
+			table.Append(row)
+			allEmpty = false
+			index++
+		}
+	}
+	if allEmpty {
+		if opts.Verbose {
+			return fmt.Sprintf("No unused resources found in the namespace: %q\n", namespace)
+		}
+		return ""
+	}
+
+	table.Render()
+	return fmt.Sprintf("Unused resources in namespace: %q\n%s\n", namespace, buf.String())
+}
+
+func formatOutputForResource(resource string, resources map[string][]ResourceInfo, opts Opts) string {
+	if len(resources) == 0 {
+		if opts.Verbose {
+			return fmt.Sprintf("No unused %ss found\n", resource)
+		}
+		return ""
+	}
+	var buf bytes.Buffer
+	table := tablewriter.NewWriter(&buf)
+	table.SetColWidth(60)
+	table.SetHeader(getTableHeader(opts.GroupBy, opts.ShowReason))
+	var index int
+	for ns, infos := range resources {
+		for _, info := range infos {
+			row := getTableRow(index, ns, info.Name)
+			if opts.ShowReason && info.Reason != "" {
+				row = append(row, info.Reason)
+			}
+			table.Append(row)
+			index++
+		}
+	}
+	table.Render()
+	return fmt.Sprintf("Unused %ss:\n%s", resource, buf.String())
+}
+
+func appendResources(resources map[string]map[string][]ResourceInfo, resourceType, namespace string, diff []ResourceInfo) {
+	for _, d := range diff {
+		if _, ok := resources[resourceType]; !ok {
+			resources[resourceType] = make(map[string][]ResourceInfo)
+		}
+		resources[resourceType][namespace] = append(resources[resourceType][namespace], d)
+	}
+}
+
+func getTableHeader(groupBy string, showReason bool) []string {
+	switch groupBy {
+	case "namespace":
+		if showReason {
+			return []string{
+				"#",
+				"RESOURCE TYPE",
+				"RESOURCE NAME",
+				"REASON",
+			}
+		}
+		return []string{
+			"#",
+			"RESOURCE TYPE",
+			"RESOURCE NAME",
+		}
+	case "resource":
+		if showReason {
+			return []string{
+				"#",
+				"NAMESPACE",
+				"RESOURCE NAME",
+				"REASON",
+			}
+		}
+		return []string{
+			"#",
+			"NAMESPACE",
+			"RESOURCE NAME",
+		}
+	default:
+		return nil
+	}
+}
+
+func getTableRowResourceInfo(index int, resourceType string, resource ResourceInfo, ShowReason bool) []string {
+	row := []string{
+		fmt.Sprintf("%d", index+1),
+		resourceType,
+		resource.Name,
+	}
+	if ShowReason && resource.Reason != "" {
+		row = append(row, resource.Reason)
+	}
+	return row
+}
+
+func FormatOutputAll(namespace string, allDiffs []ResourceDiff, opts Opts) string {
+	var buf strings.Builder
+	table := tablewriter.NewWriter(&buf)
+	table.SetColWidth(60)
+	table.SetHeader(getTableHeader(opts.GroupBy, opts.ShowReason))
+	allEmpty := true
+	var index int
+	for _, data := range allDiffs {
+		for _, info := range data.diff {
+			row := getTableRowResourceInfo(index, data.resourceType, info, opts.ShowReason)
+			table.Append(row)
+			allEmpty = false
+			index++
+		}
+	}
+	if allEmpty {
+		if opts.Verbose {
+			return fmt.Sprintf("No unused resources found in the namespace: %q\n", namespace)
+		}
+		return ""
+	}
+
+	table.Render()
+	return fmt.Sprintf("Unused resources in namespace: %q\n%s\n", namespace, buf.String())
+}
diff --git a/pkg/kor/hpas.go b/pkg/kor/hpas.go
index b4a8047..a3ff947 100644
--- a/pkg/kor/hpas.go
+++ b/pkg/kor/hpas.go
@@ -39,7 +39,7 @@ func getStatefulSetNames(clientset kubernetes.Interface, namespace string) ([]st
 	return names, nil
 }
 
-func processNamespaceHpas(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+func processNamespaceHpas(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	deploymentNames, err := getDeploymentNames(clientset, namespace)
 	if err != nil {
 		return nil, err
@@ -55,25 +55,25 @@ func processNamespaceHpas(clientset kubernetes.Interface, namespace string, filt
 		return nil, err
 	}
 
-	var unusedHpas []string
+	var unusedHpas []ResourceInfo
 	for _, hpa := range hpas.Items {
 		if pass, _ := filter.SetObject(&hpa).Run(filterOpts); pass {
 			continue
 		}
 
 		if hpa.Labels["kor/used"] == "false" {
-			unusedHpas = append(unusedHpas, hpa.Name)
+			unusedHpas = append(unusedHpas, ResourceInfo{Name: hpa.Name, Reason: "Marked with unused label"})
 			continue
 		}
 
 		switch hpa.Spec.ScaleTargetRef.Kind {
 		case "Deployment":
 			if !slices.Contains(deploymentNames, hpa.Spec.ScaleTargetRef.Name) {
-				unusedHpas = append(unusedHpas, hpa.Name)
+				unusedHpas = append(unusedHpas, ResourceInfo{Name: hpa.Name, Reason: "Scale target Deployment does not exist"})
 			}
 		case "StatefulSet":
 			if !slices.Contains(statefulsetNames, hpa.Spec.ScaleTargetRef.Name) {
-				unusedHpas = append(unusedHpas, hpa.Name)
+				unusedHpas = append(unusedHpas, ResourceInfo{Name: hpa.Name, Reason: "Scale target StatefulSet does not exist"})
 			}
 		}
 	}
@@ -81,7 +81,7 @@ func processNamespaceHpas(clientset kubernetes.Interface, namespace string, filt
 }
 
 func GetUnusedHpas(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespaceHpas(clientset, namespace, filterOpts)
 		if err != nil {
@@ -90,13 +90,13 @@ func GetUnusedHpas(filterOpts *filters.Options, clientset kubernetes.Interface,
 		}
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
+			resources[namespace] = make(map[string][]ResourceInfo)
 			resources[namespace]["Hpa"] = diff
 		case "resource":
 			appendResources(resources, "Hpa", namespace, diff)
 		}
 		if opts.DeleteFlag {
-			if diff, err = DeleteResource(diff, clientset, namespace, "HPA", opts.NoInteractive); err != nil {
+			if diff, err = DeleteResource2(diff, clientset, namespace, "HPA", opts.NoInteractive); err != nil {
 				fmt.Fprintf(os.Stderr, "Failed to delete HPA %s in namespace %s: %v\n", diff, namespace, err)
 			}
 		}
diff --git a/pkg/kor/ingresses.go b/pkg/kor/ingresses.go
index f4ad87b..51d4554 100644
--- a/pkg/kor/ingresses.go
+++ b/pkg/kor/ingresses.go
@@ -90,7 +90,7 @@ func retrieveIngressNames(clientset kubernetes.Interface, namespace string, filt
 	return names, unusedIngressNames, nil
 }
 
-func processNamespaceIngresses(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+func processNamespaceIngresses(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	usedIngresses, err := retrieveUsedIngress(clientset, namespace, filterOpts)
 	if err != nil {
 		return nil, err
@@ -100,14 +100,24 @@ func processNamespaceIngresses(clientset kubernetes.Interface, namespace string,
 		return nil, err
 	}
 
-	diff := CalculateResourceDifference(usedIngresses, ingressNames)
-	diff = append(diff, unusedIngressNames...)
+	var diff []ResourceInfo
+
+	for _, name := range CalculateResourceDifference(usedIngresses, ingressNames) {
+		reason := "Ingress does not have a valid backend service"
+		diff = append(diff, ResourceInfo{Name: name, Reason: reason})
+	}
+
+	for _, name := range unusedIngressNames {
+		reason := "Marked with unused label"
+		diff = append(diff, ResourceInfo{Name: name, Reason: reason})
+	}
+
 	return diff, nil
 
 }
 
 func GetUnusedIngresses(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespaceIngresses(clientset, namespace, filterOpts)
 		if err != nil {
@@ -116,13 +126,13 @@ func GetUnusedIngresses(filterOpts *filters.Options, clientset kubernetes.Interf
 		}
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
+			resources[namespace] = make(map[string][]ResourceInfo)
 			resources[namespace]["Ingress"] = diff
 		case "resource":
 			appendResources(resources, "Ingress", namespace, diff)
 		}
 		if opts.DeleteFlag {
-			if diff, err = DeleteResource(diff, clientset, namespace, "Ingress", opts.NoInteractive); err != nil {
+			if diff, err = DeleteResource2(diff, clientset, namespace, "Ingress", opts.NoInteractive); err != nil {
 				fmt.Fprintf(os.Stderr, "Failed to delete Ingress %s in namespace %s: %v\n", diff, namespace, err)
 			}
 		}
diff --git a/pkg/kor/jobs.go b/pkg/kor/jobs.go
index 90e74fc..1112ccc 100644
--- a/pkg/kor/jobs.go
+++ b/pkg/kor/jobs.go
@@ -18,7 +18,7 @@ import (
 //go:embed exceptions/jobs/jobs.json
 var jobsConfig []byte
 
-func processNamespaceJobs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+func processNamespaceJobs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	jobsList, err := clientset.BatchV1().Jobs(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
@@ -29,10 +29,10 @@ func processNamespaceJobs(clientset kubernetes.Interface, namespace string, filt
 		return nil, err
 	}
 
-	var unusedJobNames []string
+	var unusedJobNames []ResourceInfo
 
 	for _, job := range jobsList.Items {
-		if pass, _ := filter.Run(filterOpts); pass {
+		if pass, _ := filter.SetObject(&job).Run(filterOpts); pass {
 			continue
 		}
 
@@ -47,13 +47,15 @@ func processNamespaceJobs(clientset kubernetes.Interface, namespace string, filt
 
 		// if the job has completionTime and succeeded count greater than zero, think the job is completed
 		if job.Status.CompletionTime != nil && job.Status.Succeeded > 0 {
-			unusedJobNames = append(unusedJobNames, job.Name)
+			reason := "Job has completed"
+			unusedJobNames = append(unusedJobNames, ResourceInfo{Name: job.Name, Reason: reason})
 			continue
 		} else {
 			// Check if the job has a condition indicating it has exceeded the backoff limit
 			for _, condition := range job.Status.Conditions {
 				if condition.Type == batchv1.JobFailed && condition.Reason == "BackoffLimitExceeded" {
-					unusedJobNames = append(unusedJobNames, job.Name)
+					reason := "Job has exceeded backoff limit"
+					unusedJobNames = append(unusedJobNames, ResourceInfo{Name: job.Name, Reason: reason})
 					break
 				}
 			}
@@ -64,7 +66,7 @@ func processNamespaceJobs(clientset kubernetes.Interface, namespace string, filt
 }
 
 func GetUnusedJobs(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespaceJobs(clientset, namespace, filterOpts)
 		if err != nil {
@@ -73,13 +75,13 @@ func GetUnusedJobs(filterOpts *filters.Options, clientset kubernetes.Interface,
 		}
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
+			resources[namespace] = make(map[string][]ResourceInfo)
 			resources[namespace]["Job"] = diff
 		case "resource":
 			appendResources(resources, "Job", namespace, diff)
 		}
 		if opts.DeleteFlag {
-			if diff, err = DeleteResource(diff, clientset, namespace, "Job", opts.NoInteractive); err != nil {
+			if diff, err = DeleteResource2(diff, clientset, namespace, "Job", opts.NoInteractive); err != nil {
 				fmt.Fprintf(os.Stderr, "Failed to delete Job %s in namespace %s: %v\n", diff, namespace, err)
 			}
 		}
diff --git a/pkg/kor/kor.go b/pkg/kor/kor.go
index a501771..7648081 100644
--- a/pkg/kor/kor.go
+++ b/pkg/kor/kor.go
@@ -1,7 +1,6 @@
 package kor
 
 import (
-	"bytes"
 	"encoding/json"
 	"fmt"
 	"os"
@@ -9,14 +8,12 @@ import (
 	"regexp"
 	"sort"
 
-	"github.com/olekukonko/tablewriter"
 	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 	"k8s.io/client-go/dynamic"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/client-go/util/homedir"
-	"sigs.k8s.io/yaml"
 )
 
 type ExceptionResource struct {
@@ -51,6 +48,7 @@ type Opts struct {
 	Channel       string
 	Token         string
 	GroupBy       string
+	ShowReason    bool
 }
 
 func RemoveDuplicatesAndSort(slice []string) []string {
@@ -132,127 +130,6 @@ func GetDynamicClient(kubeconfig string) *dynamic.DynamicClient {
 	return clientset
 }
 
-func appendResources(resources map[string]map[string][]string, resourceType, namespace string, diff []string) {
-	for _, d := range diff {
-		if _, ok := resources[resourceType]; !ok {
-			resources[resourceType] = make(map[string][]string)
-		}
-		resources[resourceType][namespace] = append(resources[resourceType][namespace], d)
-	}
-}
-
-func getTableHeader(groupBy string) []string {
-	switch groupBy {
-	case "namespace":
-		return []string{
-			"#",
-			"RESOURCE TYPE",
-			"RESOURCE NAME",
-		}
-	case "resource":
-		return []string{
-			"#",
-			"NAMESPACE",
-			"RESOURCE NAME",
-		}
-	default:
-		return nil
-	}
-}
-
-func getTableRow(index int, columns ...string) []string {
-	row := make([]string, 0, len(columns)+1)
-	row = append(row, fmt.Sprintf("%d", index+1))
-	row = append(row, columns...)
-	return row
-}
-
-// FormatOutput formats the output based on the group by option
-func FormatOutput(resources map[string]map[string][]string, opts Opts) bytes.Buffer {
-	var output bytes.Buffer
-	switch opts.GroupBy {
-	case "namespace":
-		for namespace, diffs := range resources {
-			output.WriteString(formatOutputForNamespace(namespace, diffs, opts))
-		}
-	case "resource":
-		for resource, diffs := range resources {
-			output.WriteString(formatOutputForResource(resource, diffs, opts))
-		}
-	}
-	return output
-}
-
-func formatOutputForResource(resource string, resources map[string][]string, opts Opts) string {
-	if len(resources) == 0 {
-		if opts.Verbose {
-			return fmt.Sprintf("No unused %ss found\n", resource)
-		}
-		return ""
-	}
-	var buf bytes.Buffer
-	table := tablewriter.NewWriter(&buf)
-	table.SetHeader(getTableHeader(opts.GroupBy))
-	var index int
-	for ns, diffs := range resources {
-		for _, d := range diffs {
-			row := getTableRow(index, ns, d)
-			table.Append(row)
-			index++
-		}
-	}
-	table.Render()
-	return fmt.Sprintf("Unused %ss:\n%s\n", resource, buf.String())
-}
-
-func formatOutputForNamespace(namespace string, resources map[string][]string, opts Opts) string {
-	var buf bytes.Buffer
-	table := tablewriter.NewWriter(&buf)
-	table.SetHeader(getTableHeader(opts.GroupBy))
-	allEmpty := true
-	var index int
-	for resourceType, diff := range resources {
-		for _, val := range diff {
-			row := getTableRow(index, resourceType, val)
-			table.Append(row)
-			allEmpty = false
-			index++
-		}
-	}
-	if allEmpty {
-		if opts.Verbose {
-			return fmt.Sprintf("No unused resources found in the namespace: %q\n", namespace)
-		}
-		return ""
-	}
-	table.Render()
-	return fmt.Sprintf("Unused resources in namespace: %q\n%s\n", namespace, buf.String())
-}
-
-func FormatOutputAll(namespace string, allDiffs []ResourceDiff, opts Opts) string {
-	var buf bytes.Buffer
-	table := tablewriter.NewWriter(&buf)
-	table.SetHeader(getTableHeader(opts.GroupBy))
-	allEmpty := true
-	var index int
-	for _, data := range allDiffs {
-		for _, val := range data.diff {
-			row := getTableRow(index, data.resourceType, val)
-			table.Append(row)
-			allEmpty = false
-			index++
-		}
-	}
-	if allEmpty {
-		if opts.Verbose {
-			return fmt.Sprintf("No unused resources found in the namespace: %q\n", namespace)
-		}
-		return ""
-	}
-	table.Render()
-	return fmt.Sprintf("Unused resources in namespace: %q\n%s\n", namespace, buf.String())
-}
-
 // TODO create formatter by resource "#", "Resource Name", "Namespace"
 // TODO Functions that use this object are accompanied by repeated data acquisition operations and can be optimized.
 func CalculateResourceDifference(usedResourceNames []string, allResourceNames []string) []string {
@@ -272,26 +149,7 @@ func CalculateResourceDifference(usedResourceNames []string, allResourceNames []
 	return difference
 }
 
-func unusedResourceFormatter(outputFormat string, outputBuffer bytes.Buffer, opts Opts, jsonResponse []byte) (string, error) {
-	switch outputFormat {
-	case "table":
-		if opts.WebhookURL == "" || opts.Channel == "" || opts.Token != "" {
-			return outputBuffer.String(), nil
-		}
-		if err := SendToSlack(SlackMessage{}, opts, outputBuffer.String()); err != nil {
-			return "", fmt.Errorf("failed to send message to slack: %w", err)
-		}
-	case "yaml":
-		yamlResponse, err := yaml.JSONToYAML(jsonResponse)
-		if err != nil {
-			return "", fmt.Errorf("failed to convert json to yaml: %w", err)
-		}
-		return string(yamlResponse), nil
-	}
-	return string(jsonResponse), nil
-}
-
-func isResourceException(resourceName string, namespace string, exceptions []ExceptionResource) (bool, error) {
+func isResourceException(resourceName, namespace string, exceptions []ExceptionResource) (bool, error) {
 	var match bool
 	for _, e := range exceptions {
 		if e.ResourceName == resourceName && e.Namespace == namespace {
@@ -324,3 +182,21 @@ func unmarshalConfig(data []byte) (*Config, error) {
 	}
 	return &config, nil
 }
+
+func contains(slice []string, item string) bool {
+	for _, element := range slice {
+		if element == item {
+			return true
+		}
+	}
+	return false
+}
+
+func resourceInfoContains(slice []ResourceInfo, item string) bool {
+	for _, element := range slice {
+		if element.Name == item {
+			return true
+		}
+	}
+	return false
+}
diff --git a/pkg/kor/multi.go b/pkg/kor/multi.go
index be462a5..9a57a1a 100644
--- a/pkg/kor/multi.go
+++ b/pkg/kor/multi.go
@@ -100,7 +100,7 @@ func GetUnusedMulti(resourceNames string, filterOpts *filters.Options, clientset
 	var unusedMulti string
 	resourceList := strings.Split(resourceNames, ",")
 	namespaces := filterOpts.Namespaces(clientset)
-	response := make(map[string]map[string][]string)
+	response := make(map[string]map[string][]ResourceInfo)
 	var err error
 
 	noNamespaceDiff, resourceList := retrieveNoNamespaceDiff(clientset, apiExtClient, dynamicClient, resourceList, filterOpts)
@@ -110,13 +110,13 @@ func GetUnusedMulti(resourceNames string, filterOpts *filters.Options, clientset
 				output := FormatOutputAll("", []ResourceDiff{diff}, opts)
 				outputBuffer.WriteString(output)
 
-				resourceMap := make(map[string][]string)
+				resourceMap := make(map[string][]ResourceInfo)
 				resourceMap[diff.resourceType] = diff.diff
 				response[""] = resourceMap
 			}
 		}
 
-		resourceMap := make(map[string][]string)
+		resourceMap := make(map[string][]ResourceInfo)
 		for _, diff := range noNamespaceDiff {
 			resourceMap[diff.resourceType] = diff.diff
 		}
@@ -129,7 +129,7 @@ func GetUnusedMulti(resourceNames string, filterOpts *filters.Options, clientset
 
 		if opts.DeleteFlag {
 			for _, diff := range allDiffs {
-				if diff.diff, err = DeleteResource(diff.diff, clientset, namespace, diff.resourceType, opts.NoInteractive); err != nil {
+				if diff.diff, err = DeleteResource2(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)
 				}
 			}
@@ -139,7 +139,7 @@ func GetUnusedMulti(resourceNames string, filterOpts *filters.Options, clientset
 		if output != "" {
 			outputBuffer.WriteString(output)
 
-			resourceMap := make(map[string][]string)
+			resourceMap := make(map[string][]ResourceInfo)
 			for _, diff := range allDiffs {
 				resourceMap[diff.resourceType] = diff.diff
 			}
diff --git a/pkg/kor/pdbs.go b/pkg/kor/pdbs.go
index 1fbbeec..6cca118 100644
--- a/pkg/kor/pdbs.go
+++ b/pkg/kor/pdbs.go
@@ -14,8 +14,8 @@ import (
 	"github.com/yonahd/kor/pkg/filters"
 )
 
-func processNamespacePdbs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
-	var unusedPdbs []string
+func processNamespacePdbs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) {
+	var unusedPdbs []ResourceInfo
 	pdbs, err := clientset.PolicyV1().PodDisruptionBudgets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
@@ -27,17 +27,20 @@ func processNamespacePdbs(clientset kubernetes.Interface, namespace string, filt
 		}
 
 		if pdb.Labels["kor/used"] == "false" {
-			unusedPdbs = append(unusedPdbs, pdb.Name)
+			reason := "Marked with unused label"
+			unusedPdbs = append(unusedPdbs, ResourceInfo{Name: pdb.Name, Reason: reason})
 			continue
 		}
 
 		selector := pdb.Spec.Selector
 		if selector == nil {
-			unusedPdbs = append(unusedPdbs, pdb.Name)
+			reason := "Pdb has no selector"
+			unusedPdbs = append(unusedPdbs, ResourceInfo{Name: pdb.Name, Reason: reason})
 			continue
 		}
 		if len(selector.MatchLabels) == 0 {
-			unusedPdbs = append(unusedPdbs, pdb.Name)
+			reason := "Pdb has no selector"
+			unusedPdbs = append(unusedPdbs, ResourceInfo{Name: pdb.Name, Reason: reason})
 			continue
 		}
 		deployments, err := clientset.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{
@@ -53,14 +56,15 @@ func processNamespacePdbs(clientset kubernetes.Interface, namespace string, filt
 			return nil, err
 		}
 		if len(deployments.Items) == 0 && len(statefulSets.Items) == 0 {
-			unusedPdbs = append(unusedPdbs, pdb.Name)
+			reason := "Pdb is not referencing any deployments or statefulsets"
+			unusedPdbs = append(unusedPdbs, ResourceInfo{Name: pdb.Name, Reason: reason})
 		}
 	}
 	return unusedPdbs, nil
 }
 
 func GetUnusedPdbs(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespacePdbs(clientset, namespace, filterOpts)
 		if err != nil {
@@ -69,13 +73,13 @@ func GetUnusedPdbs(filterOpts *filters.Options, clientset kubernetes.Interface,
 		}
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
+			resources[namespace] = make(map[string][]ResourceInfo)
 			resources[namespace]["Pdb"] = diff
 		case "resource":
 			appendResources(resources, "Pdb", namespace, diff)
 		}
 		if opts.DeleteFlag {
-			if diff, err = DeleteResource(diff, clientset, namespace, "PDB", opts.NoInteractive); err != nil {
+			if diff, err = DeleteResource2(diff, clientset, namespace, "PDB", opts.NoInteractive); err != nil {
 				fmt.Fprintf(os.Stderr, "Failed to delete PDB %s in namespace %s: %v\n", diff, namespace, err)
 			}
 		}
diff --git a/pkg/kor/pods.go b/pkg/kor/pods.go
index 4607944..15d01a2 100644
--- a/pkg/kor/pods.go
+++ b/pkg/kor/pods.go
@@ -14,13 +14,13 @@ import (
 	"github.com/yonahd/kor/pkg/filters"
 )
 
-func processNamespacePods(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+func processNamespacePods(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	podsList, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
 
-	var evictedPods []string
+	var evictedPods []ResourceInfo
 
 	for _, pod := range podsList.Items {
 		if pass := filters.KorLabelFilter(&pod, &filters.Options{}); pass {
@@ -28,12 +28,14 @@ func processNamespacePods(clientset kubernetes.Interface, namespace string, filt
 		}
 
 		if pod.Labels["kor/used"] == "false" {
-			evictedPods = append(evictedPods, pod.Name)
+			reason := "Marked with unused label"
+			evictedPods = append(evictedPods, ResourceInfo{Name: pod.Name, Reason: reason})
 			continue
 		}
 
 		if pod.Status.Phase == corev1.PodFailed && pod.Status.Reason == "Evicted" {
-			evictedPods = append(evictedPods, pod.Name)
+			reason := "Pod is evicted"
+			evictedPods = append(evictedPods, ResourceInfo{Name: pod.Name, Reason: reason})
 		}
 
 	}
@@ -42,7 +44,7 @@ func processNamespacePods(clientset kubernetes.Interface, namespace string, filt
 }
 
 func GetUnusedPods(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespacePods(clientset, namespace, filterOpts)
 		if err != nil {
@@ -51,13 +53,13 @@ func GetUnusedPods(filterOpts *filters.Options, clientset kubernetes.Interface,
 		}
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
+			resources[namespace] = make(map[string][]ResourceInfo)
 			resources[namespace]["Pod"] = diff
 		case "resource":
 			appendResources(resources, "Pod", namespace, diff)
 		}
 		if opts.DeleteFlag {
-			if diff, err = DeleteResource(diff, clientset, namespace, "Pod", opts.NoInteractive); err != nil {
+			if diff, err = DeleteResource2(diff, clientset, namespace, "Pod", opts.NoInteractive); err != nil {
 				fmt.Fprintf(os.Stderr, "Failed to delete Pod %s in namespace %s: %v\n", diff, namespace, err)
 			}
 		}
diff --git a/pkg/kor/pv.go b/pkg/kor/pv.go
index 7789378..0e53e29 100644
--- a/pkg/kor/pv.go
+++ b/pkg/kor/pv.go
@@ -14,13 +14,13 @@ import (
 	"github.com/yonahd/kor/pkg/filters"
 )
 
-func processPvs(clientset kubernetes.Interface, filterOpts *filters.Options) ([]string, error) {
+func processPvs(clientset kubernetes.Interface, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	pvs, err := clientset.CoreV1().PersistentVolumes().List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
 
-	var unusedPvs []string
+	var unusedPvs []ResourceInfo
 
 	for _, pv := range pvs.Items {
 		if pass := filters.KorLabelFilter(&pv, &filters.Options{}); pass {
@@ -28,12 +28,14 @@ func processPvs(clientset kubernetes.Interface, filterOpts *filters.Options) ([]
 		}
 
 		if pv.Labels["kor/used"] == "false" {
-			unusedPvs = append(unusedPvs, pv.Name)
+			reason := "Marked with unused label"
+			unusedPvs = append(unusedPvs, ResourceInfo{Name: pv.Name, Reason: reason})
 			continue
 		}
 
 		if pv.Status.Phase != "Bound" {
-			unusedPvs = append(unusedPvs, pv.Name)
+			reason := "Persistent Volume is not in use"
+			unusedPvs = append(unusedPvs, ResourceInfo{Name: pv.Name, Reason: reason})
 		}
 
 	}
@@ -43,20 +45,20 @@ func processPvs(clientset kubernetes.Interface, filterOpts *filters.Options) ([]
 }
 
 func GetUnusedPvs(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	diff, err := processPvs(clientset, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to process pvs: %v\n", err)
 	}
 	switch opts.GroupBy {
 	case "namespace":
-		resources[""] = make(map[string][]string)
+		resources[""] = make(map[string][]ResourceInfo)
 		resources[""]["Pv"] = diff
 	case "resource":
 		appendResources(resources, "Pv", "", diff)
 	}
 	if opts.DeleteFlag {
-		if diff, err = DeleteResource(diff, clientset, "", "PV", opts.NoInteractive); err != nil {
+		if diff, err = DeleteResource2(diff, clientset, "", "PV", opts.NoInteractive); err != nil {
 			fmt.Fprintf(os.Stderr, "Failed to delete PV %s: %v\n", diff, err)
 		}
 	}
diff --git a/pkg/kor/pvc.go b/pkg/kor/pvc.go
index ecd1c91..c5fea1a 100644
--- a/pkg/kor/pvc.go
+++ b/pkg/kor/pvc.go
@@ -32,7 +32,7 @@ func retrieveUsedPvcs(clientset kubernetes.Interface, namespace string) ([]strin
 	return usedPvcs, err
 }
 
-func processNamespacePvcs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+func processNamespacePvcs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	pvcs, err := clientset.CoreV1().PersistentVolumeClaims(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
@@ -58,13 +58,22 @@ func processNamespacePvcs(clientset kubernetes.Interface, namespace string, filt
 		return nil, err
 	}
 
-	diff := CalculateResourceDifference(usedPvcs, pvcNames)
-	diff = append(diff, unusedPvcNames...)
+	var diff []ResourceInfo
+	for _, name := range CalculateResourceDifference(usedPvcs, pvcNames) {
+		reason := "PVC is not in use"
+		diff = append(diff, ResourceInfo{Name: name, Reason: reason})
+	}
+
+	for _, name := range unusedPvcNames {
+		reason := "Marked with unused label"
+		diff = append(diff, ResourceInfo{Name: name, Reason: reason})
+	}
+
 	return diff, nil
 }
 
 func GetUnusedPvcs(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespacePvcs(clientset, namespace, filterOpts)
 		if err != nil {
@@ -73,13 +82,13 @@ func GetUnusedPvcs(filterOpts *filters.Options, clientset kubernetes.Interface,
 		}
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
+			resources[namespace] = make(map[string][]ResourceInfo)
 			resources[namespace]["Pvc"] = diff
 		case "resource":
 			appendResources(resources, "Pvc", namespace, diff)
 		}
 		if opts.DeleteFlag {
-			if diff, err = DeleteResource(diff, clientset, namespace, "PVC", opts.NoInteractive); err != nil {
+			if diff, err = DeleteResource2(diff, clientset, namespace, "PVC", opts.NoInteractive); err != nil {
 				fmt.Fprintf(os.Stderr, "Failed to delete PVC %s in namespace %s: %v\n", diff, namespace, err)
 			}
 		}
diff --git a/pkg/kor/replicaset.go b/pkg/kor/replicaset.go
index c707e4d..1358786 100644
--- a/pkg/kor/replicaset.go
+++ b/pkg/kor/replicaset.go
@@ -13,13 +13,13 @@ import (
 	"github.com/yonahd/kor/pkg/filters"
 )
 
-func processNamespaceReplicaSets(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+func processNamespaceReplicaSets(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	replicaSetList, err := clientset.AppsV1().ReplicaSets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
 
-	var unusedReplicaSetNames []string
+	var unusedReplicaSetNames []ResourceInfo
 
 	for _, replicaSet := range replicaSetList.Items {
 		if pass, _ := filter.Run(filterOpts); pass {
@@ -28,7 +28,8 @@ func processNamespaceReplicaSets(clientset kubernetes.Interface, namespace strin
 
 		// if the replicaSet is specified 0 replica and current available & ready & fullyLabeled replica count is all 0, think the replicaSet is completed
 		if *replicaSet.Spec.Replicas == 0 && replicaSet.Status.AvailableReplicas == 0 && replicaSet.Status.ReadyReplicas == 0 && replicaSet.Status.FullyLabeledReplicas == 0 {
-			unusedReplicaSetNames = append(unusedReplicaSetNames, replicaSet.Name)
+			reason := "ReplicaSet is not in use"
+			unusedReplicaSetNames = append(unusedReplicaSetNames, ResourceInfo{Name: replicaSet.Name, Reason: reason})
 		}
 	}
 
@@ -36,7 +37,7 @@ func processNamespaceReplicaSets(clientset kubernetes.Interface, namespace strin
 }
 
 func GetUnusedReplicaSets(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespaceReplicaSets(clientset, namespace, filterOpts)
 		if err != nil {
@@ -45,13 +46,13 @@ func GetUnusedReplicaSets(filterOpts *filters.Options, clientset kubernetes.Inte
 		}
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
+			resources[namespace] = make(map[string][]ResourceInfo)
 			resources[namespace]["ReplicaSet"] = diff
 		case "resource":
 			appendResources(resources, "ReplicaSet", namespace, diff)
 		}
 		if opts.DeleteFlag {
-			if diff, err = DeleteResource(diff, clientset, namespace, "ReplicaSet", opts.NoInteractive); err != nil {
+			if diff, err = DeleteResource2(diff, clientset, namespace, "ReplicaSet", opts.NoInteractive); err != nil {
 				fmt.Fprintf(os.Stderr, "Failed to delete ReplicaSet %s in namespace %s: %v\n", diff, namespace, err)
 			}
 		}
diff --git a/pkg/kor/roles.go b/pkg/kor/roles.go
index c56a249..2cf287d 100644
--- a/pkg/kor/roles.go
+++ b/pkg/kor/roles.go
@@ -76,7 +76,7 @@ func retrieveRoleNames(clientset kubernetes.Interface, namespace string, filterO
 	return names, unusedRoleNames, nil
 }
 
-func processNamespaceRoles(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+func processNamespaceRoles(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	usedRoles, err := retrieveUsedRoles(clientset, namespace, filterOpts)
 	if err != nil {
 		return nil, err
@@ -84,19 +84,28 @@ func processNamespaceRoles(clientset kubernetes.Interface, namespace string, fil
 
 	usedRoles = RemoveDuplicatesAndSort(usedRoles)
 
-	roleNames, rolesUnusedFromLabel, err := retrieveRoleNames(clientset, namespace, filterOpts)
+	roleInfos, rolesUnusedFromLabel, err := retrieveRoleNames(clientset, namespace, filterOpts)
 	if err != nil {
 		return nil, err
 	}
 
-	diff := CalculateResourceDifference(usedRoles, roleNames)
-	diff = append(diff, rolesUnusedFromLabel...)
-	return diff, nil
+	var diff []ResourceInfo
+
+	for _, name := range CalculateResourceDifference(usedRoles, roleInfos) {
+		reason := "ServiceAccount is not in use"
+		diff = append(diff, ResourceInfo{Name: name, Reason: reason})
+	}
 
+	for _, name := range rolesUnusedFromLabel {
+		reason := "Marked with unused label"
+		diff = append(diff, ResourceInfo{Name: name, Reason: reason})
+	}
+
+	return diff, nil
 }
 
 func GetUnusedRoles(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespaceRoles(clientset, namespace, filterOpts)
 		if err != nil {
@@ -105,13 +114,13 @@ func GetUnusedRoles(filterOpts *filters.Options, clientset kubernetes.Interface,
 		}
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
+			resources[namespace] = make(map[string][]ResourceInfo)
 			resources[namespace]["Role"] = diff
 		case "resource":
 			appendResources(resources, "Role", namespace, diff)
 		}
 		if opts.DeleteFlag {
-			if diff, err = DeleteResource(diff, clientset, namespace, "Role", opts.NoInteractive); err != nil {
+			if diff, err = DeleteResource2(diff, clientset, namespace, "Role", opts.NoInteractive); err != nil {
 				fmt.Fprintf(os.Stderr, "Failed to delete Role %s in namespace %s: %v\n", diff, namespace, err)
 			}
 		}
diff --git a/pkg/kor/secrets.go b/pkg/kor/secrets.go
index 9b6216a..194a229 100644
--- a/pkg/kor/secrets.go
+++ b/pkg/kor/secrets.go
@@ -146,7 +146,7 @@ func retrieveSecretNames(clientset kubernetes.Interface, namespace string, filte
 	return names, unusedSecretNames, nil
 }
 
-func processNamespaceSecret(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+func processNamespaceSecret(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	envSecrets, envSecrets2, volumeSecrets, initContainerEnvSecrets, pullSecrets, tlsSecrets, err := retrieveUsedSecret(clientset, namespace)
 	if err != nil {
 		return nil, err
@@ -177,14 +177,25 @@ func processNamespaceSecret(clientset kubernetes.Interface, namespace string, fi
 	for _, slice := range slicesToAppend {
 		usedSecrets = append(usedSecrets, slice...)
 	}
-	diff := CalculateResourceDifference(usedSecrets, secretNames)
-	diff = append(diff, unusedSecretNames...)
+
+	var diff []ResourceInfo
+
+	for _, name := range CalculateResourceDifference(usedSecrets, secretNames) {
+		reason := "Secret is not used in any pod, container, or ingress"
+		diff = append(diff, ResourceInfo{Name: name, Reason: reason})
+	}
+
+	for _, name := range unusedSecretNames {
+		reason := "Marked with unused label"
+		diff = append(diff, ResourceInfo{Name: name, Reason: reason})
+	}
+
 	return diff, nil
 
 }
 
 func GetUnusedSecrets(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespaceSecret(clientset, namespace, filterOpts)
 		if err != nil {
@@ -193,13 +204,13 @@ func GetUnusedSecrets(filterOpts *filters.Options, clientset kubernetes.Interfac
 		}
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
+			resources[namespace] = make(map[string][]ResourceInfo)
 			resources[namespace]["Secret"] = diff
 		case "resource":
 			appendResources(resources, "Secret", namespace, diff)
 		}
 		if opts.DeleteFlag {
-			if diff, err = DeleteResource(diff, clientset, namespace, "Secret", opts.NoInteractive); err != nil {
+			if diff, err = DeleteResource2(diff, clientset, namespace, "Secret", opts.NoInteractive); err != nil {
 				fmt.Fprintf(os.Stderr, "Failed to delete Secret %s in namespace %s: %v\n", diff, namespace, err)
 			}
 		}
diff --git a/pkg/kor/serviceaccounts.go b/pkg/kor/serviceaccounts.go
index 949efed..02a0106 100644
--- a/pkg/kor/serviceaccounts.go
+++ b/pkg/kor/serviceaccounts.go
@@ -120,7 +120,7 @@ func retrieveServiceAccountNames(clientset kubernetes.Interface, namespace strin
 	return names, unusedServiceAccountNames, nil
 }
 
-func processNamespaceSA(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+func processNamespaceSA(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	usedServiceAccounts, roleServiceAccounts, clusterRoleServiceAccounts, err := retrieveUsedSA(clientset, namespace)
 	if err != nil {
 		return nil, err
@@ -141,12 +141,10 @@ func processNamespaceSA(clientset kubernetes.Interface, namespace string, filter
 		return nil, err
 	}
 
-	diff := CalculateResourceDifference(usedServiceAccounts, serviceAccountNames)
-	diff = append(diff, unusedServiceAccountNames...)
+	var unusedServiceAccounts []ResourceInfo
 
-	var result []string
-	for _, saName := range diff {
-		exceptionFound, err := isResourceException(saName, namespace, config.ExceptionServiceAccounts)
+	for _, name := range CalculateResourceDifference(usedServiceAccounts, serviceAccountNames) {
+		exceptionFound, err := isResourceException(name, namespace, config.ExceptionServiceAccounts)
 		if err != nil {
 			return nil, err
 		}
@@ -154,14 +152,19 @@ func processNamespaceSA(clientset kubernetes.Interface, namespace string, filter
 		if exceptionFound {
 			continue
 		}
-		result = append(result, saName)
+		reason := "ServiceAccount is not in use"
+		unusedServiceAccounts = append(unusedServiceAccounts, ResourceInfo{Name: name, Reason: reason})
 	}
-	return result, nil
 
+	for _, name := range unusedServiceAccountNames {
+		reason := "Marked with unused label"
+		unusedServiceAccounts = append(unusedServiceAccounts, ResourceInfo{Name: name, Reason: reason})
+	}
+	return unusedServiceAccounts, nil
 }
 
 func GetUnusedServiceAccounts(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespaceSA(clientset, namespace, filterOpts)
 		if err != nil {
@@ -170,13 +173,13 @@ func GetUnusedServiceAccounts(filterOpts *filters.Options, clientset kubernetes.
 		}
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
+			resources[namespace] = make(map[string][]ResourceInfo)
 			resources[namespace]["ServiceAccount"] = diff
 		case "resource":
 			appendResources(resources, "ServiceAccount", namespace, diff)
 		}
 		if opts.DeleteFlag {
-			if diff, err = DeleteResource(diff, clientset, namespace, "ServiceAccount", opts.NoInteractive); err != nil {
+			if diff, err = DeleteResource2(diff, clientset, namespace, "ServiceAccount", opts.NoInteractive); err != nil {
 				fmt.Fprintf(os.Stderr, "Failed to delete Serviceaccount %s in namespace %s: %v\n", diff, namespace, err)
 			}
 		}
diff --git a/pkg/kor/services.go b/pkg/kor/services.go
index b6175d4..f0d5059 100644
--- a/pkg/kor/services.go
+++ b/pkg/kor/services.go
@@ -17,13 +17,13 @@ import (
 //go:embed exceptions/services/services.json
 var servicesConfig []byte
 
-func processNamespaceServices(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+func processNamespaceServices(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	endpointsList, err := clientset.CoreV1().Endpoints(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
 
-	var endpointsWithoutSubsets []string
+	var endpointsWithoutSubsets []ResourceInfo
 
 	for _, endpoints := range endpointsList.Items {
 		if pass, _ := filter.Run(filterOpts); pass {
@@ -44,13 +44,15 @@ func processNamespaceServices(clientset kubernetes.Interface, namespace string,
 			continue
 		}
 
+		status := ResourceInfo{Name: endpoints.Name}
+
 		if endpoints.Labels["kor/used"] == "false" {
-			endpointsWithoutSubsets = append(endpointsWithoutSubsets, endpoints.Name)
+			status.Reason = "Marked with unused label"
+			endpointsWithoutSubsets = append(endpointsWithoutSubsets, status)
 			continue
-		}
-
-		if len(endpoints.Subsets) == 0 {
-			endpointsWithoutSubsets = append(endpointsWithoutSubsets, endpoints.Name)
+		} else if len(endpoints.Subsets) == 0 {
+			status.Reason = "Service has no endpoints"
+			endpointsWithoutSubsets = append(endpointsWithoutSubsets, status)
 		}
 	}
 
@@ -58,22 +60,30 @@ func processNamespaceServices(clientset kubernetes.Interface, namespace string,
 }
 
 func GetUnusedServices(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
+
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespaceServices(clientset, namespace, filterOpts)
 		if err != nil {
 			fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err)
 			continue
 		}
+
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
-			resources[namespace]["Service"] = diff
+			if diff != nil {
+				resources[namespace] = make(map[string][]ResourceInfo)
+				resources[namespace]["Service"] = diff
+			}
 		case "resource":
-			appendResources(resources, "Service", namespace, diff)
+			if diff != nil {
+				appendResources(resources, "Service", namespace, diff)
+			}
+
 		}
+
 		if opts.DeleteFlag {
-			if diff, err = DeleteResource(diff, clientset, namespace, "Service", opts.NoInteractive); err != nil {
+			if diff, err = DeleteResource2(diff, clientset, namespace, "Service", opts.NoInteractive); err != nil {
 				fmt.Fprintf(os.Stderr, "Failed to delete Service %s in namespace %s: %v\n", diff, namespace, err)
 			}
 		}
diff --git a/pkg/kor/statefulsets.go b/pkg/kor/statefulsets.go
index bd04eb4..5369d28 100644
--- a/pkg/kor/statefulsets.go
+++ b/pkg/kor/statefulsets.go
@@ -13,26 +13,30 @@ import (
 	"github.com/yonahd/kor/pkg/filters"
 )
 
-func processNamespaceStatefulSets(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+func processNamespaceStatefulSets(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	statefulSetsList, err := clientset.AppsV1().StatefulSets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
 
-	var statefulSetsWithoutReplicas []string
+	var statefulSetsWithoutReplicas []ResourceInfo
 
 	for _, statefulSet := range statefulSetsList.Items {
 		if pass, _ := filter.Run(filterOpts); pass {
 			continue
 		}
 
+		status := ResourceInfo{Name: statefulSet.Name}
+
 		if statefulSet.Labels["kor/used"] == "false" {
-			statefulSetsWithoutReplicas = append(statefulSetsWithoutReplicas, statefulSet.Name)
+			status.Reason = "Marked with unused label"
+			statefulSetsWithoutReplicas = append(statefulSetsWithoutReplicas, status)
 			continue
 		}
 
 		if *statefulSet.Spec.Replicas == 0 {
-			statefulSetsWithoutReplicas = append(statefulSetsWithoutReplicas, statefulSet.Name)
+			status.Reason = "StatefulSet has no replicas"
+			statefulSetsWithoutReplicas = append(statefulSetsWithoutReplicas, status)
 		}
 	}
 
@@ -40,7 +44,7 @@ func processNamespaceStatefulSets(clientset kubernetes.Interface, namespace stri
 }
 
 func GetUnusedStatefulSets(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespaceStatefulSets(clientset, namespace, filterOpts)
 		if err != nil {
@@ -49,13 +53,17 @@ func GetUnusedStatefulSets(filterOpts *filters.Options, clientset kubernetes.Int
 		}
 		switch opts.GroupBy {
 		case "namespace":
-			resources[namespace] = make(map[string][]string)
-			resources[namespace]["StatefulSet"] = diff
+			if diff != nil {
+				resources[namespace] = make(map[string][]ResourceInfo)
+				resources[namespace]["StatefulSet"] = diff
+			}
 		case "resource":
-			appendResources(resources, "StatefulSet", namespace, diff)
+			if diff != nil {
+				appendResources(resources, "StatefulSet", namespace, diff)
+			}
 		}
 		if opts.DeleteFlag {
-			if diff, err = DeleteResource(diff, clientset, namespace, "StatefulSet", opts.NoInteractive); err != nil {
+			if diff, err = DeleteResource2(diff, clientset, namespace, "StatefulSet", opts.NoInteractive); err != nil {
 				fmt.Fprintf(os.Stderr, "Failed to delete Statefulset %s in namespace %s: %v\n", diff, namespace, err)
 			}
 		}
diff --git a/pkg/kor/storageclasses.go b/pkg/kor/storageclasses.go
index 4a3ed11..165d203 100644
--- a/pkg/kor/storageclasses.go
+++ b/pkg/kor/storageclasses.go
@@ -50,13 +50,13 @@ func retrieveUsedStorageClasses(clientset kubernetes.Interface) ([]string, error
 	return usedStorageClasses, err
 }
 
-func processStorageClasses(clientset kubernetes.Interface, filterOpts *filters.Options) ([]string, error) {
+func processStorageClasses(clientset kubernetes.Interface, filterOpts *filters.Options) ([]ResourceInfo, error) {
 	scs, err := clientset.StorageV1().StorageClasses().List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
 
-	var unusedStorageClassNames []string
+	var unusedStorageClasses []ResourceInfo
 	storageClassNames := make([]string, 0, len(scs.Items))
 
 	for _, sc := range scs.Items {
@@ -65,7 +65,7 @@ func processStorageClasses(clientset kubernetes.Interface, filterOpts *filters.O
 		}
 
 		if sc.Labels["kor/used"] == "false" {
-			unusedStorageClassNames = append(unusedStorageClassNames, sc.Name)
+			unusedStorageClasses = append(unusedStorageClasses, ResourceInfo{Name: sc.Name, Reason: "Marked with unused label"})
 			continue
 		}
 
@@ -92,25 +92,27 @@ func processStorageClasses(clientset kubernetes.Interface, filterOpts *filters.O
 	}
 
 	diff := CalculateResourceDifference(usedStorageClasses, storageClassNames)
-	diff = append(diff, unusedStorageClassNames...)
-	return diff, nil
+	for _, name := range diff {
+		unusedStorageClasses = append(unusedStorageClasses, ResourceInfo{Name: name, Reason: "Not in Use"})
+	}
+	return unusedStorageClasses, nil
 }
 
 func GetUnusedStorageClasses(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
-	resources := make(map[string]map[string][]string)
+	resources := make(map[string]map[string][]ResourceInfo)
 	diff, err := processStorageClasses(clientset, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to process storageClasses: %v\n", err)
 	}
 	switch opts.GroupBy {
 	case "namespace":
-		resources[""] = make(map[string][]string)
+		resources[""] = make(map[string][]ResourceInfo)
 		resources[""]["StorageClass"] = diff
 	case "resource":
 		appendResources(resources, "StorageClass", "", diff)
 	}
 	if opts.DeleteFlag {
-		if diff, err = DeleteResource(diff, clientset, "", "StorageClass", opts.NoInteractive); err != nil {
+		if diff, err = DeleteResource2(diff, clientset, "", "StorageClass", opts.NoInteractive); err != nil {
 			fmt.Fprintf(os.Stderr, "Failed to delete StorageClass %s: %v\n", diff, err)
 		}
 	}


Test patch:
diff --git a/pkg/kor/clusterroles_test.go b/pkg/kor/clusterroles_test.go
index 919f471..0a8adf5 100644
--- a/pkg/kor/clusterroles_test.go
+++ b/pkg/kor/clusterroles_test.go
@@ -130,7 +130,7 @@ func TestProcessClusterRoles(t *testing.T) {
 		t.Errorf("Expected 2 unused role, got %d", len(unusedClusterRoles))
 	}
 
-	if unusedClusterRoles[0] != "test-clusterRole1" && unusedClusterRoles[1] != "test-clusterRole5" {
+	if unusedClusterRoles[0].Name != "test-clusterRole1" && unusedClusterRoles[1].Name != "test-clusterRole5" {
 		t.Errorf("Expected 'test-clusterRole1', 'test-clusterRole5', got %s, %s", unusedClusterRoles[0], unusedClusterRoles[1])
 	}
 }
diff --git a/pkg/kor/configmaps_test.go b/pkg/kor/configmaps_test.go
index 8c7e137..0214088 100644
--- a/pkg/kor/configmaps_test.go
+++ b/pkg/kor/configmaps_test.go
@@ -147,11 +147,11 @@ func TestProcessNamespaceCM(t *testing.T) {
 		t.Fatalf("Error processing namespace CM: %v", err)
 	}
 
-	unusedConfigmaps := []string{
-		"configmap-3",
-		"configmap-5",
+	unusedConfigmaps := []ResourceInfo{
+		{Name: "configmap-3", Reason: "ConfigMap is not used in any pod or container"},
+		{Name: "configmap-5", Reason: "Marked with unused label"},
 	}
-	if !equalSlices(diff, unusedConfigmaps) {
+	if !equalResourceInfoSlices(diff, unusedConfigmaps) {
 		t.Errorf("Expected diff %v, got %v", unusedConfigmaps, diff)
 	}
 }
diff --git a/pkg/kor/daemonsets_test.go b/pkg/kor/daemonsets_test.go
index c09c4f4..78bf326 100644
--- a/pkg/kor/daemonsets_test.go
+++ b/pkg/kor/daemonsets_test.go
@@ -74,7 +74,7 @@ func TestProcessNamespaceDaemonSets(t *testing.T) {
 		t.Errorf("Expected 1 DaemonSet without replicas, got %d", len(daemonSetsWithoutReplicas))
 	}
 
-	if daemonSetsWithoutReplicas[0] != "test-ds1" && daemonSetsWithoutReplicas[1] != "test-ds4" {
+	if daemonSetsWithoutReplicas[0].Name != "test-ds1" && daemonSetsWithoutReplicas[1].Name != "test-ds4" {
 		t.Errorf("Expected 'test-ds1', 'test-ds4', got %s, %s", daemonSetsWithoutReplicas[0], daemonSetsWithoutReplicas[1])
 	}
 }
diff --git a/pkg/kor/delete_test.go b/pkg/kor/delete_test.go
index 6090d07..224771b 100644
--- a/pkg/kor/delete_test.go
+++ b/pkg/kor/delete_test.go
@@ -48,6 +48,7 @@ func TestDeleteDeleteResourceWithFinalizer(t *testing.T) {
 	scheme := runtime.NewScheme()
 	gvr := schema.GroupVersionResource{Group: "testgroup", Version: "v1", Resource: "TestResource"}
 	testResource := CreateTestUnstructered(gvr.Resource, gvr.GroupVersion().String(), testNamespace, "test-resource")
+	testResouceInfo := ResourceInfo{Name: testResource.GetName()}
 	dynamicClient := fakedynamic.NewSimpleDynamicClient(scheme, testResource)
 
 	_, err := dynamicClient.Resource(gvr).
@@ -70,14 +71,14 @@ func TestDeleteDeleteResourceWithFinalizer(t *testing.T) {
 
 	tests := []struct {
 		name          string
-		diff          []string
+		diff          []ResourceInfo
 		resourceType  string
 		expectedDiff  []string
 		expectedError bool
 	}{
 		{
 			name:          "Test deletion confirmation",
-			diff:          []string{testResource.GetName()},
+			diff:          []ResourceInfo{testResouceInfo},
 			expectedDiff:  []string{testResource.GetName() + "-DELETED"},
 			expectedError: false,
 		},
@@ -88,11 +89,11 @@ func TestDeleteDeleteResourceWithFinalizer(t *testing.T) {
 			deletedDiff, _ := DeleteResourceWithFinalizer(test.diff, dynamicClient, testNamespace, gvr, true)
 
 			for i, deleted := range deletedDiff {
-				if deleted != test.expectedDiff[i] {
+				if deleted.Name != test.expectedDiff[i] {
 					t.Errorf("Expected: %s, Got: %s", test.expectedDiff[i], deleted)
 					resource, err := dynamicClient.Resource(gvr).
 						Namespace(testNamespace).
-						Get(context.TODO(), deleted, metav1.GetOptions{})
+						Get(context.TODO(), deleted.Name, metav1.GetOptions{})
 					if err != nil {
 						t.Error(err)
 					}
diff --git a/pkg/kor/deployments_test.go b/pkg/kor/deployments_test.go
index 9ea5b21..f659d6f 100644
--- a/pkg/kor/deployments_test.go
+++ b/pkg/kor/deployments_test.go
@@ -66,7 +66,7 @@ func TestProcessNamespaceDeployments(t *testing.T) {
 		t.Errorf("Expected 1 deployment without replicas, got %d", len(deploymentsWithoutReplicas))
 	}
 
-	if deploymentsWithoutReplicas[0] != "test-deployment1" && deploymentsWithoutReplicas[1] != "test-deployment4" {
+	if deploymentsWithoutReplicas[0].Name != "test-deployment1" && deploymentsWithoutReplicas[1].Name != "test-deployment4" {
 		t.Errorf("Expected 'test-deployment1', 'test-deployment4',got %s, %s", deploymentsWithoutReplicas[0], deploymentsWithoutReplicas[1])
 	}
 }
diff --git a/pkg/kor/finalizers_test.go b/pkg/kor/finalizers_test.go
index df79095..c0c8985 100644
--- a/pkg/kor/finalizers_test.go
+++ b/pkg/kor/finalizers_test.go
@@ -92,10 +92,19 @@ func TestRetrievePendingDeletionResources(t *testing.T) {
 				t.Errorf("Expected error: %v, Got: %v", test.expectedError, err)
 			}
 			if deletedResources, ok := result[testNamespace][gvr.GroupVersion().WithResource("testresources")]; ok {
-				if !slices.Equal(deletedResources, test.expectedResult) {
+				deletedResourceNames := extractNames(deletedResources)
+				if !slices.Equal(deletedResourceNames, test.expectedResult) {
 					t.Errorf("Expected result: %v, Got: %v", test.expectedResult, deletedResources)
 				}
 			}
 		})
 	}
 }
+
+func extractNames(resources []ResourceInfo) []string {
+	names := make([]string, len(resources))
+	for i, resource := range resources {
+		names[i] = resource.Name
+	}
+	return names
+}
diff --git a/pkg/kor/hpas_test.go b/pkg/kor/hpas_test.go
index 7f0fc95..b566edf 100644
--- a/pkg/kor/hpas_test.go
+++ b/pkg/kor/hpas_test.go
@@ -75,7 +75,7 @@ func TestExtractUnusedHpas(t *testing.T) {
 		t.Errorf("Expected 1 unused HPA, got %d", len(unusedHpas))
 	}
 
-	if unusedHpas[0] != "test-hpa2" && unusedHpas[1] != "test-hpa4" {
+	if unusedHpas[0].Name != "test-hpa2" && unusedHpas[1].Name != "test-hpa4" {
 		t.Errorf("Expected 'test-hpa2', 'test-hpa4', got %s, %s", unusedHpas[0], unusedHpas[1])
 	}
 }
diff --git a/pkg/kor/ingresses_test.go b/pkg/kor/ingresses_test.go
index f1c1ce4..5398310 100644
--- a/pkg/kor/ingresses_test.go
+++ b/pkg/kor/ingresses_test.go
@@ -78,15 +78,6 @@ func TestRetrieveUsedIngress(t *testing.T) {
 	}
 }
 
-func contains(slice []string, item string) bool {
-	for _, s := range slice {
-		if s == item {
-			return true
-		}
-	}
-	return false
-}
-
 func TestGetUnusedIngressesStructured(t *testing.T) {
 	clientset := createTestIngresses(t)
 
diff --git a/pkg/kor/jobs_test.go b/pkg/kor/jobs_test.go
index 6fe0235..5a89595 100644
--- a/pkg/kor/jobs_test.go
+++ b/pkg/kor/jobs_test.go
@@ -104,11 +104,8 @@ func TestProcessNamespaceJobs(t *testing.T) {
 		t.Errorf("Expected 3 jobs unused got %d", len(unusedJobs))
 	}
 
-	expectedJobs := []string{"test-job2", "test-job4", "test-job5"}
-	for _, jobName := range expectedJobs {
-		if !contains(unusedJobs, jobName) {
-			t.Errorf("Expected job %s to be considered unused, but it was not found", jobName)
-		}
+	if unusedJobs[0].Name != "test-job2" && unusedJobs[1].Name != "test-job4" && unusedJobs[2].Name != "test-job5" {
+		t.Errorf("job2', got %s", unusedJobs[0])
 	}
 }
 
diff --git a/pkg/kor/pdbs_test.go b/pkg/kor/pdbs_test.go
index b357e5c..8f2b6b8 100644
--- a/pkg/kor/pdbs_test.go
+++ b/pkg/kor/pdbs_test.go
@@ -85,7 +85,7 @@ func TestProcessNamespacePdbs(t *testing.T) {
 		t.Errorf("Expected 2 unused pdb, got %d", len(unusedPdbs))
 	}
 
-	if unusedPdbs[0] != "test-pdb3" && unusedPdbs[1] != "test-pdb5" {
+	if unusedPdbs[0].Name != "test-pdb3" && unusedPdbs[1].Name != "test-pdb5" {
 		t.Errorf("Expected 'test-pdb3', got %s", unusedPdbs[0])
 	}
 }
diff --git a/pkg/kor/pods_test.go b/pkg/kor/pods_test.go
index 15167c8..3cab146 100644
--- a/pkg/kor/pods_test.go
+++ b/pkg/kor/pods_test.go
@@ -106,7 +106,7 @@ func TestProcessNamespacePods(t *testing.T) {
 	}
 
 	for i, pod := range evictedPods {
-		if pod != expectedEvictedPods[i] {
+		if pod.Name != expectedEvictedPods[i] {
 			t.Errorf("Expected evicted pod %s, got %s", expectedEvictedPods[i], pod)
 		}
 	}
diff --git a/pkg/kor/pv_test.go b/pkg/kor/pv_test.go
index 3f8e550..d69e160 100644
--- a/pkg/kor/pv_test.go
+++ b/pkg/kor/pv_test.go
@@ -53,7 +53,7 @@ func TestProcessPvs(t *testing.T) {
 		t.Errorf("Expected 2 used pv, got %d", len(usedPvs))
 	}
 
-	if usedPvs[0] != "test-pv2" && usedPvs[1] != "test-pv3" {
+	if usedPvs[0].Name != "test-pv2" && usedPvs[1].Name != "test-pv3" {
 		t.Errorf("Expected 'test-pv2', got %s", usedPvs[0])
 	}
 }
diff --git a/pkg/kor/pvc_test.go b/pkg/kor/pvc_test.go
index e838c27..6547a01 100644
--- a/pkg/kor/pvc_test.go
+++ b/pkg/kor/pvc_test.go
@@ -91,7 +91,7 @@ func TestProcessNamespacePvcs(t *testing.T) {
 		t.Errorf("Expected 2 unused pvc, got %d", len(usedPvcs))
 	}
 
-	if usedPvcs[0] != "test-pvc2" {
+	if usedPvcs[0].Name != "test-pvc2" {
 		t.Errorf("Expected 'test-pvc2', got %s", usedPvcs[0])
 	}
 }
diff --git a/pkg/kor/roles_test.go b/pkg/kor/roles_test.go
index c568108..b847f33 100644
--- a/pkg/kor/roles_test.go
+++ b/pkg/kor/roles_test.go
@@ -101,7 +101,7 @@ func TestProcessNamespaceRoles(t *testing.T) {
 		t.Errorf("Expected 2 unused roles, got %d", len(unusedRoles))
 	}
 
-	if unusedRoles[0] != "test-role2" || unusedRoles[1] != "test-role4" {
+	if unusedRoles[0].Name != "test-role2" || unusedRoles[1].Name != "test-role4" {
 		t.Errorf("Expected 'test-role2', 'test-role4', got %s %s", unusedRoles[0], unusedRoles[1])
 	}
 }
diff --git a/pkg/kor/secrets_test.go b/pkg/kor/secrets_test.go
index d8b64f4..cbe066b 100644
--- a/pkg/kor/secrets_test.go
+++ b/pkg/kor/secrets_test.go
@@ -267,7 +267,7 @@ func TestProcessNamespaceSecret(t *testing.T) {
 		t.Errorf("Expected 2 used Secret objects, got %d", len(unusedSecrets))
 	}
 
-	if !contains(unusedSecrets, "test-secret3") {
+	if !resourceInfoContains(unusedSecrets, "test-secret3") {
 		t.Error("Expected specific Secret  in the list")
 	}
 
@@ -321,6 +321,18 @@ func equalSlices(a, b []string) bool {
 	return true
 }
 
+func equalResourceInfoSlices(a, b []ResourceInfo) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	for i, v := range a {
+		if v.Name != b[i].Name || v.Reason != b[i].Reason {
+			return false
+		}
+	}
+	return true
+}
+
 func init() {
 	scheme.Scheme = runtime.NewScheme()
 	_ = appsv1.AddToScheme(scheme.Scheme)
diff --git a/pkg/kor/serviceaccounts_test.go b/pkg/kor/serviceaccounts_test.go
index ec82b5a..3458fda 100644
--- a/pkg/kor/serviceaccounts_test.go
+++ b/pkg/kor/serviceaccounts_test.go
@@ -170,7 +170,7 @@ func TestProcessNamespaceSA(t *testing.T) {
 		t.Errorf("Expected 2 serviceAccount Used by pod, got %d", len(unusedServiceAccounts))
 	}
 
-	if unusedServiceAccounts[0] != "test-sa2" {
+	if unusedServiceAccounts[0].Name != "test-sa2" {
 		t.Errorf("Expected 'test-sa2', got %s", unusedServiceAccounts[0])
 	}
 }
diff --git a/pkg/kor/services_test.go b/pkg/kor/services_test.go
index cd159d6..9acf854 100644
--- a/pkg/kor/services_test.go
+++ b/pkg/kor/services_test.go
@@ -66,7 +66,7 @@ func TestGetEndpointsWithoutSubsets(t *testing.T) {
 		t.Errorf("Expected 2 service without endpoint, got %d", len(servicesWithoutEndpoints))
 	}
 
-	if servicesWithoutEndpoints[0] != "test-endpoint1" || servicesWithoutEndpoints[1] != "test-endpoint4" {
+	if servicesWithoutEndpoints[0].Name != "test-endpoint1" || servicesWithoutEndpoints[1].Name != "test-endpoint4" {
 		t.Errorf("Expected 'test-endpoint1' and 'test-endpoint4', got %s, %s", servicesWithoutEndpoints[0], servicesWithoutEndpoints[1])
 	}
 }
diff --git a/pkg/kor/statefulsets_test.go b/pkg/kor/statefulsets_test.go
index 513f730..249821b 100644
--- a/pkg/kor/statefulsets_test.go
+++ b/pkg/kor/statefulsets_test.go
@@ -66,7 +66,7 @@ func TestProcessNamespaceStatefulSets(t *testing.T) {
 		t.Errorf("Expected 2 deployment without replicas, got %d", len(statefulSetsWithoutReplicas))
 	}
 
-	if statefulSetsWithoutReplicas[0] != "test-sts1" || statefulSetsWithoutReplicas[1] != "test-sts4" {
+	if statefulSetsWithoutReplicas[0].Name != "test-sts1" || statefulSetsWithoutReplicas[1].Name != "test-sts4" {
 		t.Errorf("Expected 'test-sts1' and 'test-sts4, got %s, %s", statefulSetsWithoutReplicas[0], statefulSetsWithoutReplicas[1])
 	}
 }
diff --git a/pkg/kor/storageclasses_test.go b/pkg/kor/storageclasses_test.go
index ab856dd..8df66ef 100644
--- a/pkg/kor/storageclasses_test.go
+++ b/pkg/kor/storageclasses_test.go
@@ -61,7 +61,7 @@ func TestProcessStorageClasses(t *testing.T) {
 		t.Errorf("Expected 1 used StorageClasses, got %d", len(unusedStorageClasses))
 	}
 
-	if unusedStorageClasses[0] != "test-sc1" {
+	if unusedStorageClasses[0].Name != "test-sc1" {
 		t.Errorf("Expected 'test-sc1', got %s", unusedStorageClasses[0])
 	}
 }