Repository: yonahd/kor
Pull request #150 (2023-11-13 21:27:10)

feat: add inUse label prompt for non deleted resources
This PR covers issue #123 


Patch:
diff --git a/README.md b/README.md
index d2ebd0e..a6e6cd5 100644
--- a/README.md
+++ b/README.md
@@ -98,17 +98,18 @@ Kor provides various subcommands to identify and list unused resources. The avai
       --newer-than string           The maximum age of the resources to be considered unused. This flag cannot be used together with older-than flag. Example: --newer-than=1h2m
       --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
-      --output string               Output format (table, json or yaml) (default "table")
+  -o, --output string               Output format (table, json or yaml) (default "table")
       --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
+  -v, --verbose                     Verbose output (print empty namespaces)
 
 ```
 
 To use a specific subcommand, run `kor [subcommand] [flags]`.
 
 ```sh
-kor all --namespace my-namespace
+kor all --include-namespaces my-namespace
 ```
 
 For more information about each subcommand and its available flags, you can use the `--help` flag.
@@ -138,7 +139,7 @@ kor [subcommand] --help
 ## Deleting Unused resources
 If you want to delete resources in an interactive way using Kor you can run:
 ```sh
-kor configmap --namespace my-namespace --delete
+kor configmap --include-namespaces my-namespace --delete
 ```
 You will be prompted with:
 ```sh
@@ -147,7 +148,7 @@ Do you want to delete ConfigMap test-configmap in namespace my-namespace? (Y/N):
 
 To delete with no prompt ( ⚠️ use with caution):
 ```sh
-kor configmap --namespace my-namespace --delete --no-interactive
+kor configmap --include-namespaces my-namespace --delete --no-interactive
 ```
 
 ## Ignore Resources
diff --git a/cmd/kor/all.go b/cmd/kor/all.go
index e2af634..b03d3c4 100644
--- a/cmd/kor/all.go
+++ b/cmd/kor/all.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"github.com/yonahd/kor/pkg/kor"
+	"github.com/yonahd/kor/pkg/utils"
 )
 
 var allCmd = &cobra.Command{
@@ -19,6 +20,7 @@ var allCmd = &cobra.Command{
 		if response, err := kor.GetUnusedAll(includeExcludeLists, filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
+			utils.PrintLogo(outputFormat)
 			fmt.Println(response)
 		}
 	},
diff --git a/cmd/kor/configmaps.go b/cmd/kor/configmaps.go
index da2431b..7052f20 100644
--- a/cmd/kor/configmaps.go
+++ b/cmd/kor/configmaps.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"github.com/yonahd/kor/pkg/kor"
+	"github.com/yonahd/kor/pkg/utils"
 )
 
 var configmapCmd = &cobra.Command{
@@ -17,6 +18,7 @@ var configmapCmd = &cobra.Command{
 		if response, err := kor.GetUnusedConfigmaps(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
+			utils.PrintLogo(outputFormat)
 			fmt.Println(response)
 		}
 	},
diff --git a/cmd/kor/crds.go b/cmd/kor/crds.go
index 94b17d9..d8ec105 100644
--- a/cmd/kor/crds.go
+++ b/cmd/kor/crds.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"github.com/yonahd/kor/pkg/kor"
+	"github.com/yonahd/kor/pkg/utils"
 )
 
 var crdCmd = &cobra.Command{
@@ -18,6 +19,7 @@ var crdCmd = &cobra.Command{
 		if response, err := kor.GetUnusedCrds(apiExtClient, dynamicClient, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
+			utils.PrintLogo(outputFormat)
 			fmt.Println(response)
 		}
 
diff --git a/cmd/kor/deployments.go b/cmd/kor/deployments.go
index ba548a5..7880af4 100644
--- a/cmd/kor/deployments.go
+++ b/cmd/kor/deployments.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"github.com/yonahd/kor/pkg/kor"
+	"github.com/yonahd/kor/pkg/utils"
 )
 
 var deployCmd = &cobra.Command{
@@ -17,6 +18,7 @@ var deployCmd = &cobra.Command{
 		if response, err := kor.GetUnusedDeployments(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
+			utils.PrintLogo(outputFormat)
 			fmt.Println(response)
 		}
 	},
diff --git a/cmd/kor/hpas.go b/cmd/kor/hpas.go
index 1a0ccdc..17b0368 100644
--- a/cmd/kor/hpas.go
+++ b/cmd/kor/hpas.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"github.com/yonahd/kor/pkg/kor"
+	"github.com/yonahd/kor/pkg/utils"
 )
 
 var hpaCmd = &cobra.Command{
@@ -18,6 +19,7 @@ var hpaCmd = &cobra.Command{
 		if response, err := kor.GetUnusedHpas(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
+			utils.PrintLogo(outputFormat)
 			fmt.Println(response)
 		}
 
diff --git a/cmd/kor/ingresses.go b/cmd/kor/ingresses.go
index 6edffe6..cdf310a 100644
--- a/cmd/kor/ingresses.go
+++ b/cmd/kor/ingresses.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"github.com/yonahd/kor/pkg/kor"
+	"github.com/yonahd/kor/pkg/utils"
 )
 
 var ingressCmd = &cobra.Command{
@@ -18,6 +19,7 @@ var ingressCmd = &cobra.Command{
 		if response, err := kor.GetUnusedIngresses(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
+			utils.PrintLogo(outputFormat)
 			fmt.Println(response)
 		}
 	},
diff --git a/cmd/kor/pdbs.go b/cmd/kor/pdbs.go
index b2d0acb..d866237 100644
--- a/cmd/kor/pdbs.go
+++ b/cmd/kor/pdbs.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"github.com/yonahd/kor/pkg/kor"
+	"github.com/yonahd/kor/pkg/utils"
 )
 
 var pdbCmd = &cobra.Command{
@@ -18,6 +19,7 @@ var pdbCmd = &cobra.Command{
 		if response, err := kor.GetUnusedPdbs(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
+			utils.PrintLogo(outputFormat)
 			fmt.Println(response)
 		}
 	},
diff --git a/cmd/kor/pvc.go b/cmd/kor/pvc.go
index 22457c2..e707201 100644
--- a/cmd/kor/pvc.go
+++ b/cmd/kor/pvc.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"github.com/yonahd/kor/pkg/kor"
+	"github.com/yonahd/kor/pkg/utils"
 )
 
 var pvcCmd = &cobra.Command{
@@ -18,6 +19,7 @@ var pvcCmd = &cobra.Command{
 		if response, err := kor.GetUnusedPvcs(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
+			utils.PrintLogo(outputFormat)
 			fmt.Println(response)
 		}
 
diff --git a/cmd/kor/roles.go b/cmd/kor/roles.go
index 622c65e..c2bd69b 100644
--- a/cmd/kor/roles.go
+++ b/cmd/kor/roles.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"github.com/yonahd/kor/pkg/kor"
+	"github.com/yonahd/kor/pkg/utils"
 )
 
 var roleCmd = &cobra.Command{
@@ -18,6 +19,7 @@ var roleCmd = &cobra.Command{
 		if response, err := kor.GetUnusedRoles(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
+			utils.PrintLogo(outputFormat)
 			fmt.Println(response)
 		}
 	},
diff --git a/cmd/kor/root.go b/cmd/kor/root.go
index 4b46f82..01fcc7c 100644
--- a/cmd/kor/root.go
+++ b/cmd/kor/root.go
@@ -24,6 +24,7 @@ var rootCmd = &cobra.Command{
 		if response, err := kor.GetUnusedMulti(includeExcludeLists, resourceNames, filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
+			utils.PrintLogo(outputFormat)
 			fmt.Println(response)
 		}
 	},
@@ -38,7 +39,6 @@ var (
 )
 
 func Execute() {
-	utils.PrintLogo()
 	rootCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "k", "", "Path to kubeconfig file (optional)")
 	rootCmd.PersistentFlags().StringVarP(&includeExcludeLists.IncludeListStr, "include-namespaces", "n", "", "Namespaces to run on, splited by comma. Example: --include-namespace ns1,ns2,ns3. ")
 	rootCmd.PersistentFlags().StringVarP(&includeExcludeLists.ExcludeListStr, "exclude-namespaces", "e", "", "Namespaces to be excluded, splited by comma. Example: --exclude-namespace ns1,ns2,ns3. If --include-namespace is set, --exclude-namespaces will be ignored.")
diff --git a/cmd/kor/secrets.go b/cmd/kor/secrets.go
index 89aaac0..8f9c662 100644
--- a/cmd/kor/secrets.go
+++ b/cmd/kor/secrets.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"github.com/yonahd/kor/pkg/kor"
+	"github.com/yonahd/kor/pkg/utils"
 )
 
 var secretCmd = &cobra.Command{
@@ -18,6 +19,7 @@ var secretCmd = &cobra.Command{
 		if response, err := kor.GetUnusedSecrets(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
+			utils.PrintLogo(outputFormat)
 			fmt.Println(response)
 		}
 	},
diff --git a/cmd/kor/serviceaccounts.go b/cmd/kor/serviceaccounts.go
index 785c9c5..27e405b 100644
--- a/cmd/kor/serviceaccounts.go
+++ b/cmd/kor/serviceaccounts.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"github.com/yonahd/kor/pkg/kor"
+	"github.com/yonahd/kor/pkg/utils"
 )
 
 var serviceAccountCmd = &cobra.Command{
@@ -18,6 +19,7 @@ var serviceAccountCmd = &cobra.Command{
 		if response, err := kor.GetUnusedServiceAccounts(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
+			utils.PrintLogo(outputFormat)
 			fmt.Println(response)
 		}
 	},
diff --git a/cmd/kor/services.go b/cmd/kor/services.go
index 644f62c..bd0c197 100644
--- a/cmd/kor/services.go
+++ b/cmd/kor/services.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"github.com/yonahd/kor/pkg/kor"
+	"github.com/yonahd/kor/pkg/utils"
 )
 
 var serviceCmd = &cobra.Command{
@@ -18,6 +19,7 @@ var serviceCmd = &cobra.Command{
 		if response, err := kor.GetUnusedServices(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
+			utils.PrintLogo(outputFormat)
 			fmt.Println(response)
 		}
 	},
diff --git a/cmd/kor/statefulsets.go b/cmd/kor/statefulsets.go
index 8e7b39c..3e7206d 100644
--- a/cmd/kor/statefulsets.go
+++ b/cmd/kor/statefulsets.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/spf13/cobra"
 	"github.com/yonahd/kor/pkg/kor"
+	"github.com/yonahd/kor/pkg/utils"
 )
 
 var stsCmd = &cobra.Command{
@@ -18,6 +19,7 @@ var stsCmd = &cobra.Command{
 		if response, err := kor.GetUnusedStatefulSets(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
+			utils.PrintLogo(outputFormat)
 			fmt.Println(response)
 		}
 	},
diff --git a/pkg/kor/all.go b/pkg/kor/all.go
index d3fc978..ef33814 100644
--- a/pkg/kor/all.go
+++ b/pkg/kor/all.go
@@ -160,7 +160,7 @@ func GetUnusedAll(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOpt
 		namespacePdbDiff := getUnusedPdbs(clientset, namespace, filterOpts)
 		allDiffs = append(allDiffs, namespacePdbDiff)
 
-		output := FormatOutputAll(namespace, allDiffs)
+		output := FormatOutputAll(namespace, allDiffs, opts)
 
 		outputBuffer.WriteString(output)
 		outputBuffer.WriteString("\n")
@@ -176,7 +176,7 @@ func GetUnusedAll(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOpt
 	crdDiff := getUnusedCrds(apiExtClient, dynamicClient)
 	allDiffs = append(allDiffs, crdDiff)
 
-	output := FormatOutputAll("", allDiffs)
+	output := FormatOutputAll("", allDiffs, opts)
 
 	outputBuffer.WriteString(output)
 	outputBuffer.WriteString("\n")
diff --git a/pkg/kor/confimgmaps.go b/pkg/kor/confimgmaps.go
index d6729b0..218f4fa 100644
--- a/pkg/kor/confimgmaps.go
+++ b/pkg/kor/confimgmaps.go
@@ -17,9 +17,8 @@ var exceptionconfigmaps = []ExceptionResource{
 	{ResourceName: "kube-root-ca.crt", Namespace: "*"},
 }
 
-func retrieveUsedCM(clientset kubernetes.Interface, namespace string) ([]string, []string, []string, []string, []string, []string, error) {
+func retrieveUsedCM(clientset kubernetes.Interface, namespace string) ([]string, []string, []string, []string, []string, error) {
 	var volumesCM []string
-	var volumesProjectedCM []string
 	var envCM []string
 	var envFromCM []string
 	var envFromContainerCM []string
@@ -27,7 +26,7 @@ func retrieveUsedCM(clientset kubernetes.Interface, namespace string) ([]string,
 
 	pods, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
 	if err != nil {
-		return nil, nil, nil, nil, nil, nil, err
+		return nil, nil, nil, nil, nil, err
 	}
 
 	for _, pod := range pods.Items {
@@ -38,7 +37,7 @@ func retrieveUsedCM(clientset kubernetes.Interface, namespace string) ([]string,
 			if volume.Projected != nil {
 				for _, source := range volume.Projected.Sources {
 					if source.ConfigMap != nil {
-						volumesProjectedCM = append(volumesProjectedCM, source.ConfigMap.Name)
+						volumesCM = append(volumesCM, source.ConfigMap.Name)
 					}
 				}
 			}
@@ -80,7 +79,7 @@ func retrieveUsedCM(clientset kubernetes.Interface, namespace string) ([]string,
 		}
 	}
 
-	return volumesCM, volumesProjectedCM, envCM, envFromCM, envFromContainerCM, envFromInitContainerCM, nil
+	return volumesCM, envCM, envFromCM, envFromContainerCM, envFromInitContainerCM, nil
 }
 
 func retrieveConfigMapNames(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
@@ -111,13 +110,12 @@ func retrieveConfigMapNames(clientset kubernetes.Interface, namespace string, fi
 }
 
 func processNamespaceCM(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
-	volumesCM, volumesProjectedCM, envCM, envFromCM, envFromContainerCM, envFromInitContainerCM, err := retrieveUsedCM(clientset, namespace)
+	volumesCM, envCM, envFromCM, envFromContainerCM, envFromInitContainerCM, err := retrieveUsedCM(clientset, namespace)
 	if err != nil {
 		return nil, err
 	}
 
 	volumesCM = RemoveDuplicatesAndSort(volumesCM)
-	volumesProjectedCM = RemoveDuplicatesAndSort(volumesProjectedCM)
 	envCM = RemoveDuplicatesAndSort(envCM)
 	envFromCM = RemoveDuplicatesAndSort(envFromCM)
 	envFromContainerCM = RemoveDuplicatesAndSort(envFromContainerCM)
@@ -129,7 +127,7 @@ func processNamespaceCM(clientset kubernetes.Interface, namespace string, filter
 	}
 
 	var usedConfigMaps []string
-	slicesToAppend := [][]string{volumesCM, volumesProjectedCM, envCM, envFromCM, envFromContainerCM, envFromInitContainerCM}
+	slicesToAppend := [][]string{volumesCM, envCM, envFromCM, envFromContainerCM, envFromInitContainerCM}
 
 	for _, slice := range slicesToAppend {
 		usedConfigMaps = append(usedConfigMaps, slice...)
diff --git a/pkg/kor/delete.go b/pkg/kor/delete.go
index 5242191..e63e2bc 100644
--- a/pkg/kor/delete.go
+++ b/pkg/kor/delete.go
@@ -4,8 +4,15 @@ import (
 	"context"
 	"fmt"
 	"os"
+	"reflect"
 	"strings"
 
+	appsv1 "k8s.io/api/apps/v1"
+	autoscalingv1 "k8s.io/api/autoscaling/v1"
+	corev1 "k8s.io/api/core/v1"
+	networkingv1beta1 "k8s.io/api/networking/v1beta1"
+	policyv1beta1 "k8s.io/api/policy/v1beta1"
+	rbacv1 "k8s.io/api/rbac/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
 )
@@ -50,6 +57,84 @@ func DeleteResourceCmd() map[string]func(clientset kubernetes.Interface, namespa
 	return deleteResourceApiMap
 }
 
+func FlagResource(clientset kubernetes.Interface, namespace, resourceType, resourceName string) error {
+	resource, err := getResource(clientset, namespace, resourceType, resourceName)
+	if err != nil {
+		return err
+	}
+
+	labelField := reflect.ValueOf(resource).Elem().FieldByName("Labels")
+	if labelField.IsValid() {
+		labels := labelField.Interface().(map[string]string)
+		if labels == nil {
+			labels = make(map[string]string)
+		}
+		labels["kor/used"] = "true"
+		labelField.Set(reflect.ValueOf(labels))
+	} else {
+		return fmt.Errorf("unable to set labels for resource type: %s", resourceType)
+	}
+
+	_, err = updateResource(clientset, namespace, resourceType, resource)
+	return err
+}
+
+func updateResource(clientset kubernetes.Interface, namespace, resourceType string, resource interface{}) (interface{}, error) {
+	switch resourceType {
+	case "ConfigMap":
+		return clientset.CoreV1().ConfigMaps(namespace).Update(context.TODO(), resource.(*corev1.ConfigMap), metav1.UpdateOptions{})
+	case "Secret":
+		return clientset.CoreV1().Secrets(namespace).Update(context.TODO(), resource.(*corev1.Secret), metav1.UpdateOptions{})
+	case "Service":
+		return clientset.CoreV1().Services(namespace).Update(context.TODO(), resource.(*corev1.Service), metav1.UpdateOptions{})
+	case "Deployment":
+		return clientset.AppsV1().Deployments(namespace).Update(context.TODO(), resource.(*appsv1.Deployment), metav1.UpdateOptions{})
+	case "HPA":
+		return clientset.AutoscalingV1().HorizontalPodAutoscalers(namespace).Update(context.TODO(), resource.(*autoscalingv1.HorizontalPodAutoscaler), metav1.UpdateOptions{})
+	case "Ingress":
+		return clientset.NetworkingV1beta1().Ingresses(namespace).Update(context.TODO(), resource.(*networkingv1beta1.Ingress), metav1.UpdateOptions{})
+	case "PDB":
+		return clientset.PolicyV1beta1().PodDisruptionBudgets(namespace).Update(context.TODO(), resource.(*policyv1beta1.PodDisruptionBudget), metav1.UpdateOptions{})
+	case "Roles":
+		return clientset.RbacV1().Roles(namespace).Update(context.TODO(), resource.(*rbacv1.Role), metav1.UpdateOptions{})
+	case "PVC":
+		return clientset.CoreV1().PersistentVolumeClaims(namespace).Update(context.TODO(), resource.(*corev1.PersistentVolumeClaim), metav1.UpdateOptions{})
+	case "StatefulSet":
+		return clientset.AppsV1().StatefulSets(namespace).Update(context.TODO(), resource.(*appsv1.StatefulSet), metav1.UpdateOptions{})
+	case "ServiceAccount":
+		return clientset.CoreV1().ServiceAccounts(namespace).Update(context.TODO(), resource.(*corev1.ServiceAccount), metav1.UpdateOptions{})
+	}
+	return nil, fmt.Errorf("resource type '%s' is not supported", resourceType)
+}
+
+func getResource(clientset kubernetes.Interface, namespace, resourceType, resourceName string) (interface{}, error) {
+	switch resourceType {
+	case "ConfigMap":
+		return clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{})
+	case "Secret":
+		return clientset.CoreV1().Secrets(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{})
+	case "Service":
+		return clientset.CoreV1().Services(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{})
+	case "Deployment":
+		return clientset.AppsV1().Deployments(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{})
+	case "HPA":
+		return clientset.AutoscalingV1().HorizontalPodAutoscalers(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{})
+	case "Ingress":
+		return clientset.NetworkingV1beta1().Ingresses(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{})
+	case "PDB":
+		return clientset.PolicyV1beta1().PodDisruptionBudgets(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{})
+	case "Roles":
+		return clientset.RbacV1().Roles(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{})
+	case "PVC":
+		return clientset.CoreV1().PersistentVolumeClaims(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{})
+	case "StatefulSet":
+		return clientset.AppsV1().StatefulSets(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{})
+	case "ServiceAccount":
+		return clientset.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{})
+	}
+	return nil, fmt.Errorf("resource type '%s' is not supported", resourceType)
+}
+
 func DeleteResource(diff []string, clientset kubernetes.Interface, namespace, resourceType string, noInteractive bool) ([]string, error) {
 	deletedDiff := []string{}
 
@@ -71,6 +156,21 @@ func DeleteResource(diff []string, clientset kubernetes.Interface, namespace, re
 
 			if strings.ToLower(confirmation) != "y" && strings.ToLower(confirmation) != "yes" {
 				deletedDiff = append(deletedDiff, resourceName)
+
+				fmt.Printf("Do you want flag the resource %s %s in namespace %s as In Use? (Y/N): ", resourceType, resourceName, 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, resourceName); err != nil {
+						fmt.Fprintf(os.Stderr, "Failed to flag resource %s %s in namespace %s as In Use: %v\n", resourceType, resourceName, namespace, err)
+					}
+					continue
+				}
 				continue
 			}
 		}
diff --git a/pkg/kor/kor.go b/pkg/kor/kor.go
index 4eeae85..4cb9372 100644
--- a/pkg/kor/kor.go
+++ b/pkg/kor/kor.go
@@ -180,7 +180,7 @@ func FormatOutput(namespace string, resources []string, resourceType string, opt
 	return fmt.Sprintf("Unused %s in Namespace: %s\n%s", resourceType, namespace, buf.String())
 }
 
-func FormatOutputAll(namespace string, allDiffs []ResourceDiff) string {
+func FormatOutputAll(namespace string, allDiffs []ResourceDiff, opts Opts) string {
 	i := 0
 	var buf bytes.Buffer
 	table := tablewriter.NewWriter(&buf)
@@ -202,8 +202,10 @@ func FormatOutputAll(namespace string, allDiffs []ResourceDiff) string {
 		}
 	}
 
-	if allEmpty {
+	if opts.Verbose && allEmpty {
 		return fmt.Sprintf("No unused resources found in the namespace: %s", namespace)
+	} else if allEmpty {
+		return ""
 	}
 
 	table.Render()
diff --git a/pkg/kor/multi.go b/pkg/kor/multi.go
index 0500c5a..5c7bc2d 100644
--- a/pkg/kor/multi.go
+++ b/pkg/kor/multi.go
@@ -74,7 +74,7 @@ func GetUnusedMulti(includeExcludeLists IncludeExcludeLists, resourceNames strin
 
 	crdDiff, resourceList := retrieveNoNamespaceDiff(apiExtClient, dynamicClient, outputFormat, opts, resourceList)
 	if len(crdDiff) != 0 {
-		output := FormatOutputAll("", crdDiff)
+		output := FormatOutputAll("", crdDiff, opts)
 		outputBuffer.WriteString(output)
 		outputBuffer.WriteString("\n")
 
@@ -97,15 +97,18 @@ func GetUnusedMulti(includeExcludeLists IncludeExcludeLists, resourceNames strin
 			}
 
 		}
-		output := FormatOutputAll(namespace, allDiffs)
-		outputBuffer.WriteString(output)
-		outputBuffer.WriteString("\n")
+		output := FormatOutputAll(namespace, allDiffs, opts)
+		if output != "" {
+			outputBuffer.WriteString(output)
+			outputBuffer.WriteString("\n")
 
-		resourceMap := make(map[string][]string)
-		for _, diff := range allDiffs {
-			resourceMap[diff.resourceType] = diff.diff
+			resourceMap := make(map[string][]string)
+			for _, diff := range allDiffs {
+				resourceMap[diff.resourceType] = diff.diff
+			}
+			response[namespace] = resourceMap
 		}
-		response[namespace] = resourceMap
+
 	}
 
 	jsonResponse, err := json.MarshalIndent(response, "", "  ")
diff --git a/pkg/kor/secrets.go b/pkg/kor/secrets.go
index 864dab1..cef2fb9 100644
--- a/pkg/kor/secrets.go
+++ b/pkg/kor/secrets.go
@@ -37,7 +37,7 @@ func retrieveIngressTLS(clientset kubernetes.Interface, namespace string) ([]str
 
 }
 
-func retrieveUsedSecret(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, []string, []string, []string, []string, []string, error) {
+func retrieveUsedSecret(clientset kubernetes.Interface, namespace string) ([]string, []string, []string, []string, []string, []string, error) {
 	var envSecrets []string
 	var envSecrets2 []string
 	var volumeSecrets []string
@@ -77,7 +77,15 @@ func retrieveUsedSecret(clientset kubernetes.Interface, namespace string, filter
 			if volume.Secret != nil {
 				volumeSecrets = append(volumeSecrets, volume.Secret.SecretName)
 			}
+			if volume.Projected != nil && volume.Projected.Sources != nil {
+				for _, projectedResource := range volume.Projected.Sources {
+					if projectedResource.Secret != nil {
+						volumeSecrets = append(volumeSecrets, projectedResource.Secret.Name)
+					}
+				}
+			}
 		}
+
 		if pod.Spec.ImagePullSecrets != nil {
 			for _, secret := range pod.Spec.ImagePullSecrets {
 				pullSecrets = append(pullSecrets, secret.Name)
@@ -123,7 +131,7 @@ func retrieveSecretNames(clientset kubernetes.Interface, namespace string, filte
 }
 
 func processNamespaceSecret(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
-	envSecrets, envSecrets2, volumeSecrets, initContainerEnvSecrets, pullSecrets, tlsSecrets, err := retrieveUsedSecret(clientset, namespace, filterOpts)
+	envSecrets, envSecrets2, volumeSecrets, initContainerEnvSecrets, pullSecrets, tlsSecrets, err := retrieveUsedSecret(clientset, namespace)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/utils/banner.go b/pkg/utils/banner.go
index 5b4730a..fafa935 100644
--- a/pkg/utils/banner.go
+++ b/pkg/utils/banner.go
@@ -1,13 +1,10 @@
 package utils
 
 import (
-	"os"
-
 	"github.com/fatih/color"
 )
 
-func PrintLogo() {
-	format := "table" // match the default format
+func PrintLogo(outputFormat string) {
 	boldBlue := color.New(color.FgHiBlue, color.Bold)
 	asciiLogo := `
   _  _____  ____  
@@ -18,13 +15,8 @@ func PrintLogo() {
 `
 	// processing of the `outputFormat` happens inside of the rootCmd so this requires a pretty large change
 	// to keep the banner. Instead just loop through os args and find if the format was set and handle it there
-	for index, arg := range os.Args {
-		if arg == "-o" || arg == "--output" {
-			format = os.Args[index+1]
-		}
-	}
 
-	if format != "yaml" && format != "json" {
+	if outputFormat != "yaml" && outputFormat != "json" {
 		boldBlue.Println(asciiLogo)
 	}
 }


Test patch:
diff --git a/pkg/kor/configmaps_test.go b/pkg/kor/configmaps_test.go
index e0bd245..6930b22 100644
--- a/pkg/kor/configmaps_test.go
+++ b/pkg/kor/configmaps_test.go
@@ -130,7 +130,7 @@ func TestProcessNamespaceCM(t *testing.T) {
 func TestRetrieveUsedCM(t *testing.T) {
 	clientset := createTestConfigmaps(t)
 
-	volumesCM, volumesProjectedCM, envCM, envFromCM, envFromContainerCM, envFromInitContainerCM, err := retrieveUsedCM(clientset, testNamespace)
+	volumesCM, envCM, envFromCM, envFromContainerCM, envFromInitContainerCM, err := retrieveUsedCM(clientset, testNamespace)
 
 	if err != nil {
 		t.Fatalf("Error retrieving used ConfigMaps: %v", err)
@@ -141,11 +141,6 @@ func TestRetrieveUsedCM(t *testing.T) {
 		t.Errorf("Expected volume configmaps %v, got %v", expectedVolumesCM, volumesCM)
 	}
 
-	var expectedVolumesProjectedCM []string
-	if !equalSlices(volumesProjectedCM, expectedVolumesProjectedCM) {
-		t.Errorf("Expected volume configmaps %v, got %v", expectedVolumesProjectedCM, volumesProjectedCM)
-	}
-
 	expectedEnvCM := []string{"configmap-1"}
 	if !equalSlices(envCM, expectedEnvCM) {
 		t.Errorf("Expected env configmaps %v, got %v", expectedEnvCM, envCM)
diff --git a/pkg/kor/secrets_test.go b/pkg/kor/secrets_test.go
index c4def34..1814ba4 100644
--- a/pkg/kor/secrets_test.go
+++ b/pkg/kor/secrets_test.go
@@ -158,7 +158,7 @@ func TestRetrieveIngressTLS(t *testing.T) {
 func TestRetrieveUsedSecret(t *testing.T) {
 	clientset := createTestSecrets(t)
 
-	envSecrets, envSecrets2, volumeSecrets, initContainerEnvSecrets, pullSecrets, _, err := retrieveUsedSecret(clientset, testNamespace, &FilterOptions{})
+	envSecrets, envSecrets2, volumeSecrets, initContainerEnvSecrets, pullSecrets, _, err := retrieveUsedSecret(clientset, testNamespace)
 	if err != nil {
 		t.Fatalf("Error retrieving used secrets: %v", err)
 	}