Repository: yonahd/kor
Pull request #194 (2024-01-25 09:33:11)

Refactor some codes, add filter objects, and add support for lable filtering...
refactor&feat&fix&style:
    1. Unify parameter types and divide them into two categories: normal parameters `kor.Opts` and filter parameters `filters.Options`.
    2. `includeExcludeLists` no longer participates in `cmd args` binding, and is taken over by `filters.Options`.
    3. The initialization operations of `includeNamespace` and `excludeNamespace` are taken over by `filters.Options` and will only be executed once.
    4. Add filters.Framework object to uniformly process filtering logic.
    5. All currently supported resources are supported by the `kor/used=true` tag.
    6. Fix the problem that filterOptions.Validate does not take effect
    7. Support includeable parameters

The unit tests have passed, but due to the large scope of the changes, more tests are needed.


Forget-C: The original purpose was to abstract the `filter framework` to facilitate the addition of filter parameters. Other changes are necessary to achieve this goal. #187 
now, If we want to add a filter, we only need the following steps：
#### create filter func
```go
// FilterFunc is a filter that is a function
// If the resource is legal, return true
// example:
// deployment.Spec.Replicas > 0; return true
// meta.GetLabels()["kor/used"] == "true"; return true
type FilterFunc func(object runtime.Object, opts *Options) bool
func TestFilter(object runtime.Object, opts *Options) bool {
	if meta, ok := object.(metav1.Object); ok {
		if meta.GetLabels()["kor/used"] == "true" {
			return true
		}
	}
	return false
}
``` 
#### Set a name
```go
const  TestFilterName = "test"
```
#### Add to registry
```go
func NewDefaultRegistry() Registry {
	return Registry{
		LabelFilterName:    LabelFilter,
		AgeFilterName:      AgeFilter,
		KorLabelFilterName: KorLabelFilter,
                TestFilterName: TestFilter,
	}
}
```

Patch:
diff --git a/cmd/kor/all.go b/cmd/kor/all.go
index b03d3c4..3478c9b 100644
--- a/cmd/kor/all.go
+++ b/cmd/kor/all.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -17,7 +18,7 @@ var allCmd = &cobra.Command{
 		apiExtClient := kor.GetAPIExtensionsClient(kubeconfig)
 		dynamicClient := kor.GetDynamicClient(kubeconfig)
 
-		if response, err := kor.GetUnusedAll(includeExcludeLists, filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts); err != nil {
+		if response, err := kor.GetUnusedAll(filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/configmaps.go b/cmd/kor/configmaps.go
index 7052f20..161b2a6 100644
--- a/cmd/kor/configmaps.go
+++ b/cmd/kor/configmaps.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -15,7 +16,7 @@ var configmapCmd = &cobra.Command{
 	Args:    cobra.ExactArgs(0),
 	Run: func(cmd *cobra.Command, args []string) {
 		clientset := kor.GetKubeClient(kubeconfig)
-		if response, err := kor.GetUnusedConfigmaps(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
+		if response, err := kor.GetUnusedConfigmaps(filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/crds.go b/cmd/kor/crds.go
index d8ec105..c68cee4 100644
--- a/cmd/kor/crds.go
+++ b/cmd/kor/crds.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -16,7 +17,7 @@ var crdCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		apiExtClient := kor.GetAPIExtensionsClient(kubeconfig)
 		dynamicClient := kor.GetDynamicClient(kubeconfig)
-		if response, err := kor.GetUnusedCrds(apiExtClient, dynamicClient, outputFormat, opts); err != nil {
+		if response, err := kor.GetUnusedCrds(filterOptions, apiExtClient, dynamicClient, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/deployments.go b/cmd/kor/deployments.go
index 7880af4..4eb16d6 100644
--- a/cmd/kor/deployments.go
+++ b/cmd/kor/deployments.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -15,7 +16,7 @@ var deployCmd = &cobra.Command{
 	Args:    cobra.ExactArgs(0),
 	Run: func(cmd *cobra.Command, args []string) {
 		clientset := kor.GetKubeClient(kubeconfig)
-		if response, err := kor.GetUnusedDeployments(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
+		if response, err := kor.GetUnusedDeployments(filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/exporter.go b/cmd/kor/exporter.go
index 7327835..d146a21 100644
--- a/cmd/kor/exporter.go
+++ b/cmd/kor/exporter.go
@@ -2,6 +2,7 @@ package kor
 
 import (
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 )
 
@@ -14,7 +15,7 @@ var exporterCmd = &cobra.Command{
 		apiExtClient := kor.GetAPIExtensionsClient(kubeconfig)
 		dynamicClient := kor.GetDynamicClient(kubeconfig)
 
-		kor.Exporter(includeExcludeLists, filterOptions, clientset, apiExtClient, dynamicClient, "json", opts)
+		kor.Exporter(filterOptions, clientset, apiExtClient, dynamicClient, "json", opts)
 
 	},
 }
diff --git a/cmd/kor/finalizers.go b/cmd/kor/finalizers.go
index bd775e7..08a8acd 100644
--- a/cmd/kor/finalizers.go
+++ b/cmd/kor/finalizers.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 )
 
@@ -15,7 +16,8 @@ var finalizerCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		clientset := kor.GetKubeClient(kubeconfig)
 		dynamicClient := kor.GetDynamicClient(kubeconfig)
-		if response, err := kor.GetUnusedfinalizers(includeExcludeLists, filterOptions, clientset, dynamicClient, outputFormat, opts); err != nil {
+
+		if response, err := kor.GetUnusedfinalizers(filterOptions, clientset, dynamicClient, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			fmt.Println(response)
diff --git a/cmd/kor/hpas.go b/cmd/kor/hpas.go
index 17b0368..2679637 100644
--- a/cmd/kor/hpas.go
+++ b/cmd/kor/hpas.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -16,7 +17,7 @@ var hpaCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		clientset := kor.GetKubeClient(kubeconfig)
 
-		if response, err := kor.GetUnusedHpas(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
+		if response, err := kor.GetUnusedHpas(filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/ingresses.go b/cmd/kor/ingresses.go
index cdf310a..9ec5c91 100644
--- a/cmd/kor/ingresses.go
+++ b/cmd/kor/ingresses.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -16,7 +17,7 @@ var ingressCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		clientset := kor.GetKubeClient(kubeconfig)
 
-		if response, err := kor.GetUnusedIngresses(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
+		if response, err := kor.GetUnusedIngresses(filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/jobs.go b/cmd/kor/jobs.go
index 64b10f0..172a350 100644
--- a/cmd/kor/jobs.go
+++ b/cmd/kor/jobs.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -15,7 +16,8 @@ var jobCmd = &cobra.Command{
 	Args:    cobra.ExactArgs(0),
 	Run: func(cmd *cobra.Command, args []string) {
 		clientset := kor.GetKubeClient(kubeconfig)
-		if response, err := kor.GetUnusedJobs(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
+
+		if response, err := kor.GetUnusedJobs(filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/pdbs.go b/cmd/kor/pdbs.go
index d866237..6fecade 100644
--- a/cmd/kor/pdbs.go
+++ b/cmd/kor/pdbs.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -16,7 +17,7 @@ var pdbCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		clientset := kor.GetKubeClient(kubeconfig)
 
-		if response, err := kor.GetUnusedPdbs(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
+		if response, err := kor.GetUnusedPdbs(filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/pods.go b/cmd/kor/pods.go
index 94755ee..9c13c03 100644
--- a/cmd/kor/pods.go
+++ b/cmd/kor/pods.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -15,7 +16,8 @@ var podCmd = &cobra.Command{
 	Args:    cobra.ExactArgs(0),
 	Run: func(cmd *cobra.Command, args []string) {
 		clientset := kor.GetKubeClient(kubeconfig)
-		if response, err := kor.GetUnusedPods(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
+
+		if response, err := kor.GetUnusedPods(filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/pv.go b/cmd/kor/pv.go
index a473034..dcfc669 100644
--- a/cmd/kor/pv.go
+++ b/cmd/kor/pv.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
diff --git a/cmd/kor/pvc.go b/cmd/kor/pvc.go
index e707201..e29f9c6 100644
--- a/cmd/kor/pvc.go
+++ b/cmd/kor/pvc.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -16,7 +17,7 @@ var pvcCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		clientset := kor.GetKubeClient(kubeconfig)
 
-		if response, err := kor.GetUnusedPvcs(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
+		if response, err := kor.GetUnusedPvcs(filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/replicasets.go b/cmd/kor/replicasets.go
index cf16688..7a63852 100644
--- a/cmd/kor/replicasets.go
+++ b/cmd/kor/replicasets.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -15,7 +16,8 @@ var replicaSetCmd = &cobra.Command{
 	Args:    cobra.ExactArgs(0),
 	Run: func(cmd *cobra.Command, args []string) {
 		clientset := kor.GetKubeClient(kubeconfig)
-		if response, err := kor.GetUnusedReplicaSets(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
+
+		if response, err := kor.GetUnusedReplicaSets(filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/roles.go b/cmd/kor/roles.go
index c2bd69b..2f71739 100644
--- a/cmd/kor/roles.go
+++ b/cmd/kor/roles.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -16,7 +17,7 @@ var roleCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		clientset := kor.GetKubeClient(kubeconfig)
 
-		if response, err := kor.GetUnusedRoles(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
+		if response, err := kor.GetUnusedRoles(filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/root.go b/cmd/kor/root.go
index 01fcc7c..b4a7391 100644
--- a/cmd/kor/root.go
+++ b/cmd/kor/root.go
@@ -5,6 +5,8 @@ import (
 	"os"
 
 	"github.com/spf13/cobra"
+
+	"github.com/yonahd/kor/pkg/filters"
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -21,7 +23,7 @@ var rootCmd = &cobra.Command{
 		apiExtClient := kor.GetAPIExtensionsClient(kubeconfig)
 		dynamicClient := kor.GetDynamicClient(kubeconfig)
 
-		if response, err := kor.GetUnusedMulti(includeExcludeLists, resourceNames, filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts); err != nil {
+		if response, err := kor.GetUnusedMulti(resourceNames, filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
@@ -31,17 +33,14 @@ var rootCmd = &cobra.Command{
 }
 
 var (
-	outputFormat        string
-	kubeconfig          string
-	includeExcludeLists kor.IncludeExcludeLists
-	opts                kor.Opts
-	filterOptions       = kor.NewFilterOptions()
+	outputFormat  string
+	kubeconfig    string
+	opts          kor.Opts
+	filterOptions = &filters.Options{}
 )
 
-func Execute() {
+func init() {
 	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.")
 	rootCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "table", "Output format (table, json or yaml)")
 	rootCmd.PersistentFlags().StringVar(&opts.WebhookURL, "slack-webhook-url", "", "Slack webhook URL to send notifications to")
 	rootCmd.PersistentFlags().StringVar(&opts.Channel, "slack-channel", "", "Slack channel to send notifications to. --slack-channel requires --slack-auth-token to be set.")
@@ -50,19 +49,26 @@ func Execute() {
 	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)")
 	addFilterOptionsFlag(rootCmd, filterOptions)
+}
 
+func Execute() {
+	_ = rootCmd.ParseFlags(os.Args)
 	if err := filterOptions.Validate(); err != nil {
 		fmt.Fprintf(os.Stderr, "Error while validating filter options '%s'", err)
 		os.Exit(1)
 	}
+	filterOptions.Modify()
 	if err := rootCmd.Execute(); err != nil {
 		fmt.Fprintf(os.Stderr, "Error while executing your CLI '%s'", err)
 		os.Exit(1)
 	}
 }
 
-func addFilterOptionsFlag(cmd *cobra.Command, opts *kor.FilterOptions) {
-	cmd.PersistentFlags().StringVarP(&opts.ExcludeLabels, "exclude-labels", "l", opts.ExcludeLabels, "Selector to filter out, Example: --exclude-labels key1=value1,key2=value2.")
+func addFilterOptionsFlag(cmd *cobra.Command, opts *filters.Options) {
+	cmd.PersistentFlags().StringVarP(&opts.ExcludeLabels, "exclude-labels", "l", opts.ExcludeLabels, "Selector to filter out, Example: --exclude-labels key1=value1,key2=value2. If --include-labels is set, --exclude-labels will be ignored.")
 	cmd.PersistentFlags().StringVar(&opts.NewerThan, "newer-than", opts.NewerThan, "The maximum age of the resources to be considered unused. This flag cannot be used together with older-than flag. Example: --newer-than=1h2m")
 	cmd.PersistentFlags().StringVar(&opts.OlderThan, "older-than", opts.OlderThan, "The minimum age of the resources to be considered unused. This flag cannot be used together with newer-than flag. Example: --older-than=1h2m")
+	cmd.PersistentFlags().StringVar(&opts.IncludeLabels, "include-labels", opts.IncludeLabels, "Selector to filter in, Example: --include-labels key1=value1,key2=value2.")
+	cmd.PersistentFlags().StringSliceVar(&opts.ExcludeNamespaces, "exclude-namespaces", opts.ExcludeNamespaces, "Namespaces to be excluded, splited by comma. Example: --exclude-namespace ns1,ns2,ns3. If --include-namespace is set, --exclude-namespaces will be ignored.")
+	cmd.PersistentFlags().StringSliceVarP(&opts.IncludeNamespaces, "include-namespaces", "n", opts.IncludeNamespaces, "Namespaces to run on, splited by comma. Example: --include-namespace ns1,ns2,ns3. ")
 }
diff --git a/cmd/kor/secrets.go b/cmd/kor/secrets.go
index 8f9c662..28f4f25 100644
--- a/cmd/kor/secrets.go
+++ b/cmd/kor/secrets.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -16,7 +17,7 @@ var secretCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		clientset := kor.GetKubeClient(kubeconfig)
 
-		if response, err := kor.GetUnusedSecrets(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
+		if response, err := kor.GetUnusedSecrets(filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/serviceaccounts.go b/cmd/kor/serviceaccounts.go
index 27e405b..ba73b46 100644
--- a/cmd/kor/serviceaccounts.go
+++ b/cmd/kor/serviceaccounts.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -16,7 +17,7 @@ var serviceAccountCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		clientset := kor.GetKubeClient(kubeconfig)
 
-		if response, err := kor.GetUnusedServiceAccounts(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
+		if response, err := kor.GetUnusedServiceAccounts(filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/services.go b/cmd/kor/services.go
index bd0c197..996ca61 100644
--- a/cmd/kor/services.go
+++ b/cmd/kor/services.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -16,7 +17,7 @@ var serviceCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		clientset := kor.GetKubeClient(kubeconfig)
 
-		if response, err := kor.GetUnusedServices(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
+		if response, err := kor.GetUnusedServices(filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/statefulsets.go b/cmd/kor/statefulsets.go
index 3e7206d..dd53e17 100644
--- a/cmd/kor/statefulsets.go
+++ b/cmd/kor/statefulsets.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/spf13/cobra"
+
 	"github.com/yonahd/kor/pkg/kor"
 	"github.com/yonahd/kor/pkg/utils"
 )
@@ -16,7 +17,7 @@ var stsCmd = &cobra.Command{
 	Run: func(cmd *cobra.Command, args []string) {
 		clientset := kor.GetKubeClient(kubeconfig)
 
-		if response, err := kor.GetUnusedStatefulSets(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil {
+		if response, err := kor.GetUnusedStatefulSets(filterOptions, clientset, outputFormat, opts); err != nil {
 			fmt.Println(err)
 		} else {
 			utils.PrintLogo(outputFormat)
diff --git a/go.mod b/go.mod
index 19fcd05..da23376 100644
--- a/go.mod
+++ b/go.mod
@@ -29,6 +29,7 @@ require (
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/google/gnostic-models v0.6.8 // indirect
+	github.com/google/go-cmp v0.6.0 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/uuid v1.3.0 // indirect
 	github.com/imdario/mergo v0.3.16 // indirect
diff --git a/pkg/filters/filters.go b/pkg/filters/filters.go
new file mode 100644
index 0000000..d8eca56
--- /dev/null
+++ b/pkg/filters/filters.go
@@ -0,0 +1,98 @@
+package filters
+
+import (
+	"errors"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+const (
+	LabelFilterName    = "label"
+	AgeFilterName      = "age"
+	KorLabelFilterName = "korlabel"
+)
+
+// KorLabelFilter is a filter that filters out resources that are ["kor/used"] != "true"
+func KorLabelFilter(object runtime.Object, opts *Options) bool {
+	if meta, ok := object.(metav1.Object); ok {
+		if meta.GetLabels()["kor/used"] == "true" {
+			return true
+		}
+	}
+	return false
+}
+
+// LabelFilter is a filter that filters out resources by label
+func LabelFilter(object runtime.Object, opts *Options) bool {
+	if meta, ok := object.(metav1.Object); ok {
+		if has, err := HasExcludedLabel(meta.GetLabels(), opts.ExcludeLabels); err == nil {
+			return has
+		}
+	}
+	return false
+}
+
+// AgeFilter is a filter that filters out resources by age
+func AgeFilter(object runtime.Object, opts *Options) bool {
+	if meta, ok := object.(metav1.Object); ok {
+		if has, err := HasIncludedAge(meta.GetCreationTimestamp(), opts); err == nil {
+			return !has
+		}
+	}
+	return false
+}
+
+// HasExcludedLabel parses the excluded selector into a label selector object
+func HasExcludedLabel(resourcelabels map[string]string, excludeSelector string) (bool, error) {
+	if excludeSelector == "" {
+		return false, nil
+	}
+	exclude, err := labels.Parse(excludeSelector)
+	if err != nil {
+		return false, err
+	}
+
+	labelSet := labels.Set(resourcelabels)
+	return exclude.Matches(labelSet), nil
+}
+
+// HasIncludedAge checks if a resource has an age that matches the included criteria specified by the filter options
+// A resource is considered to have an included age if its age (measured from the last modified time) is within the
+// range specified by older-than and newer-than flags.
+// If older-than or newer-than is zero, no age limit is applied.
+// If both flags are set, an error is returned.
+func HasIncludedAge(creationTime metav1.Time, filterOpts *Options) (bool, error) {
+	if filterOpts.OlderThan == "" && filterOpts.NewerThan == "" {
+		return true, nil
+	}
+	// The function returns an error if both flags are set is because it does not make sense to
+	// query for resources that are both older than and newer than a certain duration.
+	// For example, if you set --older-than=1h and --newer-than=30m, you are asking for resources
+	// that are older than 1 hour and newer than 30 minutes, which is impossible!
+	if filterOpts.OlderThan != "" && filterOpts.NewerThan != "" {
+		return false, errors.New("invalid flags: older-than and newer-than cannot be used together")
+	}
+
+	// Parse the older-than flag value into a time.Duration value
+	if filterOpts.OlderThan != "" {
+		olderThan, err := time.ParseDuration(filterOpts.OlderThan)
+		if err != nil {
+			return false, err
+		}
+		return time.Since(creationTime.Time) > olderThan, nil
+	}
+
+	// Parse the newer-than flag value into a time.Duration value
+	if filterOpts.NewerThan != "" {
+		newerThan, err := time.ParseDuration(filterOpts.NewerThan)
+		if err != nil {
+			return false, err
+		}
+		return time.Since(creationTime.Time) < newerThan, nil
+	}
+
+	return true, nil
+}
diff --git a/pkg/filters/frameworks.go b/pkg/filters/frameworks.go
new file mode 100644
index 0000000..8af950d
--- /dev/null
+++ b/pkg/filters/frameworks.go
@@ -0,0 +1,79 @@
+package filters
+
+import (
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+func NewNormalFramework(r Registry) Framework {
+	return &normalFramework{registry: r}
+}
+
+type normalFramework struct {
+	registry Registry
+	object   runtime.Object
+}
+
+func (n *normalFramework) Run(opts *Options, disable ...string) (bool, error) {
+	for name, f := range n.registry {
+		if isIn(name, disable) {
+			continue
+		}
+		if f(n.object, opts) {
+			return true, nil
+		}
+	}
+	return false, nil
+}
+
+func (n *normalFramework) RunFilter(name string, opts *Options) (bool, error) {
+	f, ok := n.registry[name]
+	if !ok {
+		return true, nil
+	}
+	return f(n.object, opts), nil
+
+}
+
+func (n *normalFramework) AddFilter(name string, f FilterFunc) Framework {
+	out := n.DeepCopy()
+	_ = out.registry.Register(name, f)
+	return out
+}
+
+func (n *normalFramework) SetObject(object runtime.Object) Framework {
+	out := n.DeepCopy()
+	out.object = object
+	return out
+}
+
+func (n *normalFramework) SetRegistry(r Registry) Framework {
+	out := n.DeepCopy()
+	out.registry = r
+	return out
+}
+
+func (n *normalFramework) DeepCopy() *normalFramework {
+	out := new(normalFramework)
+	n.DeepCopyInto(out)
+	return out
+}
+
+func (n *normalFramework) DeepCopyInto(out *normalFramework) {
+	*out = *n
+	if n.registry != nil {
+		in, out := &n.registry, &out.registry
+		*out = make(Registry, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+func isIn(name string, disable []string) bool {
+	for _, v := range disable {
+		if v == name {
+			return true
+		}
+	}
+	return false
+}
diff --git a/pkg/filters/interface.go b/pkg/filters/interface.go
new file mode 100644
index 0000000..4d4b92e
--- /dev/null
+++ b/pkg/filters/interface.go
@@ -0,0 +1,28 @@
+package filters
+
+import (
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+// FilterFunc is a filter that is a function
+// If the resource is legal, return true
+// example:
+// deployment.Spec.Replicas > 0; return true
+// meta.GetLabels()["kor/used"] == "true"; return true
+type FilterFunc func(object runtime.Object, opts *Options) bool
+
+// Framework is a filter framework
+type Framework interface {
+	// Run runs all the filters in the framework
+	// If the resource is legal, return true
+	Run(opts *Options, disable ...string) (bool, error)
+	// AddFilter adds a filter to the framework
+	AddFilter(name string, f FilterFunc) Framework
+	// SetRegistry sets the registry of the framework
+	SetRegistry(r Registry) Framework
+	// SetObject sets the object of the framework
+	SetObject(object runtime.Object) Framework
+	// RunFilter runs a filter in the framework
+	// name not found, return true
+	RunFilter(name string, opts *Options) (bool, error)
+}
diff --git a/pkg/filters/options.go b/pkg/filters/options.go
new file mode 100644
index 0000000..f2d2165
--- /dev/null
+++ b/pkg/filters/options.go
@@ -0,0 +1,147 @@
+package filters
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"os"
+	"sync"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/client-go/kubernetes"
+)
+
+// Options represents the flags and options for filtering unused Kubernetes resources, such as pods, services, or configmaps.
+// A resource is considered unused if it meets the following conditions:
+//   - Its age (measured from the last modified time) is within the range specified by older-than and newer-than flags.
+//     If older-than or newer-than is zero, no age limit is applied.
+//     If both flags are set, an error is returned.
+//   - Its size (measured in bytes) is within the range specified by MinSize and MaxSize flags.
+//     If MinSize or MaxSize is zero, no size limit is applied.
+//   - It does not have any labels that match the ExcludeLabels flag. The ExcludeLabels flag supports '=', '==', and '!=' operators,
+//     and multiple label pairs can be separated by commas. For example, -l key1=value1,key2!=value2.
+type Options struct {
+	// OlderThan is the minimum age of the resources to be considered unused
+	OlderThan string
+	// NewerThan is the maximum age of the resources to be considered unused
+	NewerThan string
+	// ExcludeLabels is a label selector to exclude resources with matching labels
+	// IncludeLabels conflicts with it, and when setting IncludeLabels, ExcludeLabels is ignored and set to empty
+	ExcludeLabels string
+	// IncludeLabels is a label selector to include resources with matching labels
+	IncludeLabels string
+	// ExcludeNamespaces is a namespace selector to exclude resources in matching namespaces
+	// IncludeNamespaces conflicts with it, and when setting IncludeNamespaces, ExcludeNamespaces is ignored and set to empty
+	ExcludeNamespaces []string
+	// IncludeNamespaces is a namespace selector to include resources in matching namespaces
+	IncludeNamespaces []string
+
+	namespace []string
+	once      sync.Once
+}
+
+// NewFilterOptions returns a new FilterOptions instance with default values
+func NewFilterOptions() *Options {
+	return &Options{
+		OlderThan:     "",
+		NewerThan:     "",
+		ExcludeLabels: "",
+	}
+}
+
+// Validate makes sure provided values for FilterOptions are valid
+func (o *Options) Validate() error {
+	if _, err := labels.Parse(o.ExcludeLabels); err != nil {
+		return err
+	}
+	if _, err := labels.Parse(o.IncludeLabels); err != nil {
+		return err
+	}
+
+	// Parse the older-than flag value into a time.Duration value
+	if o.OlderThan != "" {
+		olderThan, err := time.ParseDuration(o.OlderThan)
+		if err != nil {
+			return err
+		}
+		if olderThan < 0 {
+			return errors.New("OlderThan must be a non-negative duration")
+		}
+	}
+
+	// Parse the newer-than flag value into a time.Duration value
+	if o.NewerThan != "" {
+		newerThan, err := time.ParseDuration(o.NewerThan)
+		if err != nil {
+			return err
+		}
+		if newerThan < 0 {
+			return errors.New("NewerThan must be a non-negative duration")
+		}
+	}
+
+	return nil
+}
+
+// Modify modifies the options
+func (o *Options) Modify() {
+	o.modifyLabels()
+}
+
+// Namespaces returns the namespaces, only called once
+func (o *Options) Namespaces(clientset kubernetes.Interface) []string {
+	o.once.Do(func() {
+		namespaces := make([]string, 0)
+		namespacesMap := make(map[string]bool)
+		if len(o.IncludeNamespaces) > 0 && len(o.ExcludeNamespaces) > 0 {
+			fmt.Fprintf(os.Stderr, "Exclude namespaces can't be used together with include namespaces. Ignoring --exclude-namespace(-e) flag\n")
+			o.ExcludeNamespaces = nil
+		}
+		includeNamespaces := o.IncludeNamespaces
+		excludeNamespaces := o.ExcludeNamespaces
+		namespaceList, err := clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to retrieve namespaces: %v\n", err)
+			os.Exit(1)
+		}
+		if len(o.IncludeNamespaces) > 0 {
+			for _, ns := range namespaceList.Items {
+				namespacesMap[ns.Name] = false
+			}
+			for _, ns := range includeNamespaces {
+				if _, exists := namespacesMap[ns]; exists {
+					namespacesMap[ns] = true
+				} else {
+					fmt.Fprintf(os.Stderr, "namespace [%s] not found\n", ns)
+				}
+			}
+		} else {
+			for _, ns := range namespaceList.Items {
+				namespacesMap[ns.Name] = true
+			}
+			for _, ns := range excludeNamespaces {
+				if _, exists := namespacesMap[ns]; exists {
+					namespacesMap[ns] = false
+				}
+			}
+		}
+		for ns := range namespacesMap {
+			if namespacesMap[ns] {
+				namespaces = append(namespaces, ns)
+			}
+		}
+		o.namespace = namespaces
+	})
+	return o.namespace
+}
+
+func (o *Options) modifyLabels() {
+	if o.IncludeLabels != "" {
+		if o.ExcludeLabels != "" {
+			fmt.Fprintf(os.Stderr, "Exclude labels can't be used together with include labels. Ignoring --exclude-label(-l) flag\n")
+		}
+		o.ExcludeLabels = ""
+	}
+}
diff --git a/pkg/filters/registry.go b/pkg/filters/registry.go
new file mode 100644
index 0000000..f92c7a7
--- /dev/null
+++ b/pkg/filters/registry.go
@@ -0,0 +1,39 @@
+package filters
+
+import "fmt"
+
+func NewDefaultRegistry() Registry {
+	return Registry{
+		LabelFilterName:    LabelFilter,
+		AgeFilterName:      AgeFilter,
+		KorLabelFilterName: KorLabelFilter,
+	}
+}
+
+// Registry is a collection of all available filters. The framework uses a
+type Registry map[string]FilterFunc
+
+func (r Registry) Register(name string, filter FilterFunc) error {
+	if _, ok := r[name]; ok {
+		return fmt.Errorf("a filter named %v already exists", name)
+	}
+	r[name] = filter
+	return nil
+}
+
+func (r Registry) Unregister(name string) error {
+	if _, ok := r[name]; !ok {
+		return fmt.Errorf("no filter named %v exists", name)
+	}
+	delete(r, name)
+	return nil
+}
+
+func (r Registry) Merge(in Registry) error {
+	for name, filter := range in {
+		if err := r.Register(name, filter); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/pkg/kor/all.go b/pkg/kor/all.go
index 3fd5b56..987c083 100644
--- a/pkg/kor/all.go
+++ b/pkg/kor/all.go
@@ -9,6 +9,8 @@ import (
 	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 	"k8s.io/client-go/dynamic"
 	"k8s.io/client-go/kubernetes"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 type GetUnusedResourceJSONResponse struct {
@@ -21,7 +23,7 @@ type ResourceDiff struct {
 	diff         []string
 }
 
-func getUnusedCMs(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff {
+func getUnusedCMs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff {
 	cmDiff, err := processNamespaceCM(clientset, namespace, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "configmaps", namespace, err)
@@ -30,7 +32,7 @@ func getUnusedCMs(clientset kubernetes.Interface, namespace string, filterOpts *
 	return namespaceCMDiff
 }
 
-func getUnusedSVCs(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff {
+func getUnusedSVCs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff {
 	svcDiff, err := ProcessNamespaceServices(clientset, namespace, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "services", namespace, err)
@@ -39,7 +41,7 @@ func getUnusedSVCs(clientset kubernetes.Interface, namespace string, filterOpts
 	return namespaceSVCDiff
 }
 
-func getUnusedSecrets(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff {
+func getUnusedSecrets(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff {
 	secretDiff, err := processNamespaceSecret(clientset, namespace, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "secrets", namespace, err)
@@ -48,7 +50,7 @@ func getUnusedSecrets(clientset kubernetes.Interface, namespace string, filterOp
 	return namespaceSecretDiff
 }
 
-func getUnusedServiceAccounts(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff {
+func getUnusedServiceAccounts(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff {
 	saDiff, err := processNamespaceSA(clientset, namespace, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "serviceaccounts", namespace, err)
@@ -57,7 +59,7 @@ func getUnusedServiceAccounts(clientset kubernetes.Interface, namespace string,
 	return namespaceSADiff
 }
 
-func getUnusedDeployments(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff {
+func getUnusedDeployments(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff {
 	deployDiff, err := ProcessNamespaceDeployments(clientset, namespace, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "deployments", namespace, err)
@@ -66,7 +68,7 @@ func getUnusedDeployments(clientset kubernetes.Interface, namespace string, filt
 	return namespaceSADiff
 }
 
-func getUnusedStatefulSets(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff {
+func getUnusedStatefulSets(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff {
 	stsDiff, err := ProcessNamespaceStatefulSets(clientset, namespace, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "statefulSets", namespace, err)
@@ -75,7 +77,7 @@ func getUnusedStatefulSets(clientset kubernetes.Interface, namespace string, fil
 	return namespaceSADiff
 }
 
-func getUnusedRoles(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff {
+func getUnusedRoles(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff {
 	roleDiff, err := processNamespaceRoles(clientset, namespace, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "roles", namespace, err)
@@ -84,7 +86,7 @@ func getUnusedRoles(clientset kubernetes.Interface, namespace string, filterOpts
 	return namespaceSADiff
 }
 
-func getUnusedHpas(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff {
+func getUnusedHpas(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff {
 	hpaDiff, err := processNamespaceHpas(clientset, namespace, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "hpas", namespace, err)
@@ -93,7 +95,7 @@ func getUnusedHpas(clientset kubernetes.Interface, namespace string, filterOpts
 	return namespaceHpaDiff
 }
 
-func getUnusedPvcs(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff {
+func getUnusedPvcs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff {
 	pvcDiff, err := processNamespacePvcs(clientset, namespace, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "pvcs", namespace, err)
@@ -102,7 +104,7 @@ func getUnusedPvcs(clientset kubernetes.Interface, namespace string, filterOpts
 	return namespacePvcDiff
 }
 
-func getUnusedIngresses(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff {
+func getUnusedIngresses(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff {
 	ingressDiff, err := processNamespaceIngresses(clientset, namespace, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "ingresses", namespace, err)
@@ -111,7 +113,7 @@ func getUnusedIngresses(clientset kubernetes.Interface, namespace string, filter
 	return namespaceIngressDiff
 }
 
-func getUnusedPdbs(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff {
+func getUnusedPdbs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff {
 	pdbDiff, err := processNamespacePdbs(clientset, namespace, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "pdbs", namespace, err)
@@ -120,8 +122,8 @@ func getUnusedPdbs(clientset kubernetes.Interface, namespace string, filterOpts
 	return namespacePdbDiff
 }
 
-func getUnusedCrds(apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface) ResourceDiff {
-	crdDiff, err := processCrds(apiExtClient, dynamicClient)
+func getUnusedCrds(apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, filterOpts *filters.Options) ResourceDiff {
+	crdDiff, err := processCrds(apiExtClient, dynamicClient, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s: %v\n", "Crds", err)
 	}
@@ -129,7 +131,7 @@ func getUnusedCrds(apiExtClient apiextensionsclientset.Interface, dynamicClient
 	return allCrdDiff
 }
 
-func getUnusedPvs(clientset kubernetes.Interface, filterOpts *FilterOptions) ResourceDiff {
+func getUnusedPvs(clientset kubernetes.Interface, filterOpts *filters.Options) ResourceDiff {
 	pvDiff, err := processPvs(clientset, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s: %v\n", "Pvs", err)
@@ -138,7 +140,7 @@ func getUnusedPvs(clientset kubernetes.Interface, filterOpts *FilterOptions) Res
 	return allPvDiff
 }
 
-func getUnusedPods(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff {
+func getUnusedPods(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff {
 	podDiff, err := ProcessNamespacePods(clientset, namespace, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "pods", namespace, err)
@@ -147,7 +149,7 @@ func getUnusedPods(clientset kubernetes.Interface, namespace string, filterOpts
 	return namespacePodDiff
 }
 
-func getUnusedJobs(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff {
+func getUnusedJobs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff {
 	jobDiff, err := ProcessNamespaceJobs(clientset, namespace, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "jobs", namespace, err)
@@ -156,7 +158,7 @@ func getUnusedJobs(clientset kubernetes.Interface, namespace string, filterOpts
 	return namespaceJobDiff
 }
 
-func getUnusedReplicaSets(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff {
+func getUnusedReplicaSets(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ResourceDiff {
 	replicaSetDiff, err := ProcessNamespaceReplicaSets(clientset, namespace, filterOpts)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "jobs", namespace, err)
@@ -165,10 +167,10 @@ func getUnusedReplicaSets(clientset kubernetes.Interface, namespace string, filt
 	return namespaceRSDiff
 }
 
-func GetUnusedAll(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedAll(filterOpts *filters.Options, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
 
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
+	namespaces := filterOpts.Namespaces(clientset)
 	response := make(map[string]map[string][]string)
 
 	for _, namespace := range namespaces {
@@ -214,7 +216,7 @@ func GetUnusedAll(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOpt
 
 	var allDiffs []ResourceDiff
 	noNamespaceResourceMap := make(map[string][]string)
-	crdDiff := getUnusedCrds(apiExtClient, dynamicClient)
+	crdDiff := getUnusedCrds(apiExtClient, dynamicClient, filterOpts)
 	crdOutput := FormatOutputAll("", []ResourceDiff{crdDiff}, opts)
 	outputBuffer.WriteString(crdOutput)
 	outputBuffer.WriteString("\n")
diff --git a/pkg/kor/confimgmaps.go b/pkg/kor/confimgmaps.go
index 218f4fa..1df15db 100644
--- a/pkg/kor/confimgmaps.go
+++ b/pkg/kor/confimgmaps.go
@@ -10,6 +10,8 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
 	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 var exceptionconfigmaps = []ExceptionResource{
@@ -82,34 +84,22 @@ func retrieveUsedCM(clientset kubernetes.Interface, namespace string) ([]string,
 	return volumesCM, envCM, envFromCM, envFromContainerCM, envFromInitContainerCM, nil
 }
 
-func retrieveConfigMapNames(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
-	configmaps, err := clientset.CoreV1().ConfigMaps(namespace).List(context.TODO(), metav1.ListOptions{})
+func retrieveConfigMapNames(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+	configmaps, err := clientset.CoreV1().ConfigMaps(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
 	names := make([]string, 0, len(configmaps.Items))
 	for _, configmap := range configmaps.Items {
-		// checks if the resource has any labels that match the excluded selector specified in opts.ExcludeLabels.
-		// If it does, the resource is skipped.
-		if excluded, _ := HasExcludedLabel(configmap.Labels, filterOpts.ExcludeLabels); excluded {
-			continue
-		}
-		// checks if the resource's age (measured from its last modified time) matches the included criteria
-		// specified by the filter options.
-		if included, _ := HasIncludedAge(configmap.CreationTimestamp, filterOpts); !included {
+		if pass, _ := filter.SetObject(&configmap).Run(filterOpts); pass {
 			continue
 		}
-
-		if configmap.Labels["kor/used"] == "true" {
-			continue
-		}
-
 		names = append(names, configmap.Name)
 	}
 	return names, nil
 }
 
-func processNamespaceCM(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
+func processNamespaceCM(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
 	volumesCM, envCM, envFromCM, envFromContainerCM, envFromInitContainerCM, err := retrieveUsedCM(clientset, namespace)
 	if err != nil {
 		return nil, err
@@ -137,12 +127,11 @@ func processNamespaceCM(clientset kubernetes.Interface, namespace string, filter
 
 }
 
-func GetUnusedConfigmaps(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedConfigmaps(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
 	response := make(map[string]map[string][]string)
 
-	for _, namespace := range namespaces {
+	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := processNamespaceCM(clientset, namespace, filterOpts)
 		if err != nil {
 			fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err)
diff --git a/pkg/kor/crds.go b/pkg/kor/crds.go
index 776cb7c..6f98732 100644
--- a/pkg/kor/crds.go
+++ b/pkg/kor/crds.go
@@ -12,19 +12,21 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/client-go/dynamic"
 	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
-func processCrds(apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface) ([]string, error) {
+func processCrds(apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, filterOpts *filters.Options) ([]string, error) {
 
 	var unusedCRDs []string
 
-	crds, err := apiExtClient.ApiextensionsV1().CustomResourceDefinitions().List(context.TODO(), metav1.ListOptions{})
+	crds, err := apiExtClient.ApiextensionsV1().CustomResourceDefinitions().List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
 
 	for _, crd := range crds.Items {
-		if crd.Labels["kor/used"] == "true" {
+		if pass := filters.KorLabelFilter(&crd, &filters.Options{}); pass {
 			continue
 		}
 
@@ -33,7 +35,7 @@ func processCrds(apiExtClient apiextensionsclientset.Interface, dynamicClient dy
 			Version:  crd.Spec.Versions[0].Name, // We're checking the first version.
 			Resource: crd.Spec.Names.Plural,
 		}
-		instances, err := dynamicClient.Resource(gvr).Namespace("").List(context.TODO(), metav1.ListOptions{})
+		instances, err := dynamicClient.Resource(gvr).Namespace("").List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 		if err != nil {
 			return nil, err
 		}
@@ -44,10 +46,10 @@ func processCrds(apiExtClient apiextensionsclientset.Interface, dynamicClient dy
 	return unusedCRDs, nil
 }
 
-func GetUnusedCrds(apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedCrds(filterOpts *filters.Options, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) (string, error) {
 
 	var outputBuffer bytes.Buffer
-	diff, err := processCrds(apiExtClient, dynamicClient)
+	diff, err := processCrds(apiExtClient, dynamicClient, &filters.Options{})
 
 	response := make(map[string]map[string][]string)
 
diff --git a/pkg/kor/deployments.go b/pkg/kor/deployments.go
index 0e6224f..931e23e 100644
--- a/pkg/kor/deployments.go
+++ b/pkg/kor/deployments.go
@@ -9,10 +9,12 @@ import (
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
-func ProcessNamespaceDeployments(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
-	deploymentsList, err := clientset.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{})
+func ProcessNamespaceDeployments(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+	deploymentsList, err := clientset.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
@@ -20,21 +22,9 @@ func ProcessNamespaceDeployments(clientset kubernetes.Interface, namespace strin
 	var deploymentsWithoutReplicas []string
 
 	for _, deployment := range deploymentsList.Items {
-		if deployment.Labels["kor/used"] == "true" {
-			continue
-		}
-
-		// checks if the resource has any labels that match the excluded selector specified in opts.ExcludeLabels.
-		// If it does, the resource is skipped.
-		if excluded, _ := HasExcludedLabel(deployment.Labels, filterOpts.ExcludeLabels); excluded {
+		if pass, _ := filter.SetObject(&deployment).Run(filterOpts); pass {
 			continue
 		}
-		// checks if the resource's age (measured from its last modified time) matches the included criteria
-		// specified by the filter options.
-		if included, _ := HasIncludedAge(deployment.CreationTimestamp, filterOpts); !included {
-			continue
-		}
-
 		if *deployment.Spec.Replicas == 0 {
 			deploymentsWithoutReplicas = append(deploymentsWithoutReplicas, deployment.Name)
 		}
@@ -43,12 +33,11 @@ func ProcessNamespaceDeployments(clientset kubernetes.Interface, namespace strin
 	return deploymentsWithoutReplicas, nil
 }
 
-func GetUnusedDeployments(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedDeployments(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
 	response := make(map[string]map[string][]string)
 
-	for _, namespace := range namespaces {
+	for _, namespace := range filterOpts.Namespaces(clientset) {
 		diff, err := ProcessNamespaceDeployments(clientset, namespace, filterOpts)
 		if err != nil {
 			fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err)
diff --git a/pkg/kor/exporter.go b/pkg/kor/exporter.go
index adb4f6d..4bdeda6 100644
--- a/pkg/kor/exporter.go
+++ b/pkg/kor/exporter.go
@@ -13,6 +13,8 @@ import (
 	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 	"k8s.io/client-go/dynamic"
 	"k8s.io/client-go/kubernetes"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 var (
@@ -30,16 +32,16 @@ func init() {
 }
 
 // TODO: add option to change port / url !?
-func Exporter(includeExcludeLists IncludeExcludeLists, filterOptions *FilterOptions, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) {
+func Exporter(filterOptions *filters.Options, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) {
 	http.Handle("/metrics", promhttp.Handler())
 	fmt.Println("Server listening on :8080")
-	go exportMetrics(includeExcludeLists, filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts) // Start exporting metrics in the background
+	go exportMetrics(filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts) // Start exporting metrics in the background
 	if err := http.ListenAndServe(":8080", nil); err != nil {
 		fmt.Println(err)
 	}
 }
 
-func exportMetrics(includeExcludeLists IncludeExcludeLists, filterOptions *FilterOptions, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) {
+func exportMetrics(filterOptions *filters.Options, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) {
 	exporterInterval := os.Getenv("EXPORTER_INTERVAL")
 	if exporterInterval == "" {
 		exporterInterval = "10"
@@ -52,7 +54,7 @@ func exportMetrics(includeExcludeLists IncludeExcludeLists, filterOptions *Filte
 
 	for {
 		fmt.Println("collecting unused resources")
-		if korOutput, err := GetUnusedAll(includeExcludeLists, filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts); err != nil {
+		if korOutput, err := GetUnusedAll(filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts); err != nil {
 			fmt.Println(err)
 			os.Exit(1)
 		} else {
diff --git a/pkg/kor/filter_options.go b/pkg/kor/filter_options.go
deleted file mode 100644
index 75de583..0000000
--- a/pkg/kor/filter_options.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package kor
-
-import (
-	"errors"
-	"time"
-
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/labels"
-)
-
-// FilterOptions represents the flags and options for filtering unused Kubernetes resources, such as pods, services, or configmaps.
-// A resource is considered unused if it meets the following conditions:
-//   - Its age (measured from the last modified time) is within the range specified by older-than and newer-than flags.
-//     If older-than or newer-than is zero, no age limit is applied.
-//     If both flags are set, an error is returned.
-//   - Its size (measured in bytes) is within the range specified by MinSize and MaxSize flags.
-//     If MinSize or MaxSize is zero, no size limit is applied.
-//   - It does not have any labels that match the ExcludeLabels flag. The ExcludeLabels flag supports '=', '==', and '!=' operators,
-//     and multiple label pairs can be separated by commas. For example, -l key1=value1,key2!=value2.
-type FilterOptions struct {
-	// OlderThan is the minimum age of the resources to be considered unused
-	OlderThan string
-	// NewerThan is the maximum age of the resources to be considered unused
-	NewerThan string
-	// ExcludeLabels is a label selector to exclude resources with matching labels
-	ExcludeLabels string
-}
-
-// NewFilterOptions returns a new FilterOptions instance with default values
-func NewFilterOptions() *FilterOptions {
-	return &FilterOptions{
-		OlderThan:     "",
-		NewerThan:     "",
-		ExcludeLabels: "",
-	}
-}
-
-// Validate makes sure provided values for FilterOptions are valid
-func (o *FilterOptions) Validate() error {
-	if _, err := labels.Parse(o.ExcludeLabels); err != nil {
-		return err
-	}
-
-	// Parse the older-than flag value into a time.Duration value
-	if o.OlderThan != "" {
-		olderThan, err := time.ParseDuration(o.OlderThan)
-		if err != nil {
-			return err
-		}
-		if olderThan < 0 {
-			return errors.New("OlderThan must be a non-negative duration")
-		}
-	}
-
-	// Parse the newer-than flag value into a time.Duration value
-	if o.NewerThan != "" {
-		newerThan, err := time.ParseDuration(o.NewerThan)
-		if err != nil {
-			return err
-		}
-		if newerThan < 0 {
-			return errors.New("NewerThan must be a non-negative duration")
-		}
-	}
-
-	return nil
-}
-
-// HasExcludedLabel parses the excluded selector into a label selector object
-func HasExcludedLabel(resourcelabels map[string]string, excludeSelector string) (bool, error) {
-	if excludeSelector == "" {
-		return false, nil
-	}
-	exclude, err := labels.Parse(excludeSelector)
-	if err != nil {
-		return false, err
-	}
-
-	labelSet := labels.Set(resourcelabels)
-	return exclude.Matches(labelSet), nil
-}
-
-// HasIncludedAge checks if a resource has an age that matches the included criteria specified by the filter options
-// A resource is considered to have an included age if its age (measured from the last modified time) is within the
-// range specified by older-than and newer-than flags.
-// If older-than or newer-than is zero, no age limit is applied.
-// If both flags are set, an error is returned.
-func HasIncludedAge(creationTime metav1.Time, filterOpts *FilterOptions) (bool, error) {
-	if filterOpts.OlderThan == "" && filterOpts.NewerThan == "" {
-		return true, nil
-	}
-	// The function returns an error if both flags are set is because it does not make sense to
-	// query for resources that are both older than and newer than a certain duration.
-	// For example, if you set --older-than=1h and --newer-than=30m, you are asking for resources
-	// that are older than 1 hour and newer than 30 minutes, which is impossible!
-	if filterOpts.OlderThan != "" && filterOpts.NewerThan != "" {
-		return false, errors.New("invalid flags: older-than and newer-than cannot be used together")
-	}
-
-	// Parse the older-than flag value into a time.Duration value
-	if filterOpts.OlderThan != "" {
-		olderThan, err := time.ParseDuration(filterOpts.OlderThan)
-		if err != nil {
-			return false, err
-		}
-		return time.Since(creationTime.Time) > olderThan, nil
-	}
-
-	// Parse the newer-than flag value into a time.Duration value
-	if filterOpts.NewerThan != "" {
-		newerThan, err := time.ParseDuration(filterOpts.NewerThan)
-		if err != nil {
-			return false, err
-		}
-		return time.Since(creationTime.Time) < newerThan, nil
-	}
-
-	return true, nil
-}
diff --git a/pkg/kor/finalizers.go b/pkg/kor/finalizers.go
index f80697a..0915489 100644
--- a/pkg/kor/finalizers.go
+++ b/pkg/kor/finalizers.go
@@ -13,6 +13,8 @@ import (
 	"k8s.io/client-go/kubernetes"
 	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
 	"k8s.io/utils/strings/slices"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func CheckFinalizers(finalizers []string, deletionTimestamp *metav1.Time) bool {
@@ -22,7 +24,7 @@ func CheckFinalizers(finalizers []string, deletionTimestamp *metav1.Time) bool {
 	return false
 }
 
-func retrievePendingDeletionResources(resourceTypes []*metav1.APIResourceList, dynamicClient dynamic.Interface, filterOpts *FilterOptions) (map[string]map[schema.GroupVersionResource][]string, error) {
+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
 
 	for _, apiResourceList := range resourceTypes {
@@ -39,24 +41,13 @@ func retrievePendingDeletionResources(resourceTypes []*metav1.APIResourceList, d
 				resourceList, err := dynamicClient.
 					Resource(gvr).
 					Namespace(metav1.NamespaceAll).
-					List(context.TODO(), metav1.ListOptions{})
+					List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 				if err != nil {
 					fmt.Printf("Error listing resources for GVR %s: %v\n", apiResourceList.GroupVersion, err)
 					continue
 				}
 				for _, item := range resourceList.Items {
-					labels := item.GetLabels()
-					if labels["kor/used"] == "true" {
-						continue
-					}
-
-					// Check for excluded labels
-					if excluded, _ := HasExcludedLabel(labels, filterOpts.ExcludeLabels); excluded {
-						continue
-					}
-
-					// Check age criteria
-					if included, _ := HasIncludedAge(item.GetCreationTimestamp(), filterOpts); !included {
+					if pass, _ := filter.Run(filterOpts); pass {
 						continue
 					}
 					if CheckFinalizers(item.GetFinalizers(), item.GetDeletionTimestamp()) {
@@ -72,7 +63,7 @@ func retrievePendingDeletionResources(resourceTypes []*metav1.APIResourceList, d
 	return pendingDeletionResources, nil
 }
 
-func getResourcesWithFinalizersPendingDeletion(clientset kubernetes.Interface, dynamicClient dynamic.Interface, filterOpts *FilterOptions) (map[string]map[schema.GroupVersionResource][]string, error) {
+func getResourcesWithFinalizersPendingDeletion(clientset kubernetes.Interface, dynamicClient dynamic.Interface, filterOpts *filters.Options) (map[string]map[schema.GroupVersionResource][]string, error) {
 	// Use the discovery client to fetch API resources
 	resourceTypes, err := clientset.Discovery().ServerPreferredNamespacedResources()
 	if err != nil {
@@ -83,9 +74,9 @@ func getResourcesWithFinalizersPendingDeletion(clientset kubernetes.Interface, d
 	return retrievePendingDeletionResources(resourceTypes, dynamicClient, filterOpts)
 }
 
-func GetUnusedfinalizers(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, dynamicClient *dynamic.DynamicClient, outputFormat string, opts Opts) (string, error) {
+func GetUnusedfinalizers(filterOpts *filters.Options, clientset kubernetes.Interface, dynamicClient *dynamic.DynamicClient, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
+	namespaces := filterOpts.Namespaces(clientset)
 	response := make(map[string]map[string][]string)
 	pendingDeletionDiffs, err := getResourcesWithFinalizersPendingDeletion(clientset, dynamicClient, filterOpts)
 
diff --git a/pkg/kor/hpas.go b/pkg/kor/hpas.go
index a5721da..553ef5b 100644
--- a/pkg/kor/hpas.go
+++ b/pkg/kor/hpas.go
@@ -11,6 +11,8 @@ import (
 	"k8s.io/client-go/kubernetes"
 	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
 	"k8s.io/utils/strings/slices"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func getDeploymentNames(clientset kubernetes.Interface, namespace string) ([]string, error) {
@@ -37,7 +39,7 @@ func getStatefulSetNames(clientset kubernetes.Interface, namespace string) ([]st
 	return names, nil
 }
 
-func extractUnusedHpas(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
+func extractUnusedHpas(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
 	deploymentNames, err := getDeploymentNames(clientset, namespace)
 	if err != nil {
 		return nil, err
@@ -46,25 +48,14 @@ func extractUnusedHpas(clientset kubernetes.Interface, namespace string, filterO
 	if err != nil {
 		return nil, err
 	}
-	hpas, err := clientset.AutoscalingV2().HorizontalPodAutoscalers(namespace).List(context.TODO(), metav1.ListOptions{})
+	hpas, err := clientset.AutoscalingV2().HorizontalPodAutoscalers(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
 
 	var diff []string
 	for _, hpa := range hpas.Items {
-		if hpa.Labels["kor/used"] == "true" {
-			continue
-		}
-
-		// checks if the resource has any labels that match the excluded selector specified in opts.ExcludeLabels.
-		// If it does, the resource is skipped.
-		if excluded, _ := HasExcludedLabel(hpa.Labels, filterOpts.ExcludeLabels); excluded {
-			continue
-		}
-		// checks if the resource's age (measured from its last modified time) matches the included criteria
-		// specified by the filter options.
-		if included, _ := HasIncludedAge(hpa.CreationTimestamp, filterOpts); !included {
+		if pass, _ := filter.Run(filterOpts); pass {
 			continue
 		}
 
@@ -82,7 +73,7 @@ func extractUnusedHpas(clientset kubernetes.Interface, namespace string, filterO
 	return diff, nil
 }
 
-func processNamespaceHpas(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
+func processNamespaceHpas(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
 	unusedHpas, err := extractUnusedHpas(clientset, namespace, filterOpts)
 	if err != nil {
 		return nil, err
@@ -90,9 +81,9 @@ func processNamespaceHpas(clientset kubernetes.Interface, namespace string, filt
 	return unusedHpas, nil
 }
 
-func GetUnusedHpas(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedHpas(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
+	namespaces := filterOpts.Namespaces(clientset)
 	response := make(map[string]map[string][]string)
 
 	for _, namespace := range namespaces {
diff --git a/pkg/kor/ingresses.go b/pkg/kor/ingresses.go
index 7d20faf..55b9cf2 100644
--- a/pkg/kor/ingresses.go
+++ b/pkg/kor/ingresses.go
@@ -11,6 +11,8 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
 	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func validateServiceBackend(clientset kubernetes.Interface, namespace string, backend *v1.IngressBackend) bool {
@@ -25,8 +27,8 @@ func validateServiceBackend(clientset kubernetes.Interface, namespace string, ba
 	return true
 }
 
-func retrieveUsedIngress(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
-	ingresses, err := clientset.NetworkingV1().Ingresses(namespace).List(context.TODO(), metav1.ListOptions{})
+func retrieveUsedIngress(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+	ingresses, err := clientset.NetworkingV1().Ingresses(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
@@ -34,14 +36,7 @@ func retrieveUsedIngress(clientset kubernetes.Interface, namespace string, filte
 	usedIngresses := []string{}
 
 	for _, ingress := range ingresses.Items {
-		// checks if the resource has any labels that match the excluded selector specified in opts.ExcludeLabels.
-		// If it does, the resource is skipped.
-		if excluded, _ := HasExcludedLabel(ingress.Labels, filterOpts.ExcludeLabels); excluded {
-			continue
-		}
-		// checks if the resource's age (measured from its last modified time) matches the included criteria
-		// specified by the filter options.
-		if included, _ := HasIncludedAge(ingress.CreationTimestamp, filterOpts); !included {
+		if pass, _ := filter.Run(filterOpts); pass {
 			continue
 		}
 
@@ -79,7 +74,7 @@ func retrieveIngressNames(clientset kubernetes.Interface, namespace string) ([]s
 	}
 	names := make([]string, 0, len(ingresses.Items))
 	for _, ingress := range ingresses.Items {
-		if ingress.Labels["kor/used"] == "true" {
+		if filters.KorLabelFilter(&ingress, &filters.Options{}) {
 			continue
 		}
 		names = append(names, ingress.Name)
@@ -87,7 +82,7 @@ func retrieveIngressNames(clientset kubernetes.Interface, namespace string) ([]s
 	return names, nil
 }
 
-func processNamespaceIngresses(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
+func processNamespaceIngresses(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
 	usedIngresses, err := retrieveUsedIngress(clientset, namespace, filterOpts)
 	if err != nil {
 		return nil, err
@@ -102,9 +97,9 @@ func processNamespaceIngresses(clientset kubernetes.Interface, namespace string,
 
 }
 
-func GetUnusedIngresses(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedIngresses(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
+	namespaces := filterOpts.Namespaces(clientset)
 	response := make(map[string]map[string][]string)
 
 	for _, namespace := range namespaces {
diff --git a/pkg/kor/init.go b/pkg/kor/init.go
new file mode 100644
index 0000000..6f0b347
--- /dev/null
+++ b/pkg/kor/init.go
@@ -0,0 +1,11 @@
+package kor
+
+import (
+	"github.com/yonahd/kor/pkg/filters"
+)
+
+var filter filters.Framework
+
+func init() {
+	filter = filters.NewNormalFramework(filters.NewDefaultRegistry())
+}
diff --git a/pkg/kor/jobs.go b/pkg/kor/jobs.go
index 8682270..6dc131d 100644
--- a/pkg/kor/jobs.go
+++ b/pkg/kor/jobs.go
@@ -5,13 +5,16 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"os"
+
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
-	"os"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
-func ProcessNamespaceJobs(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
-	jobsList, err := clientset.BatchV1().Jobs(namespace).List(context.TODO(), metav1.ListOptions{})
+func ProcessNamespaceJobs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+	jobsList, err := clientset.BatchV1().Jobs(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
@@ -19,18 +22,7 @@ func ProcessNamespaceJobs(clientset kubernetes.Interface, namespace string, filt
 	var unusedJobNames []string
 
 	for _, job := range jobsList.Items {
-		if job.Labels["kor/used"] == "true" {
-			continue
-		}
-
-		// checks if the resource has any labels that match the excluded selector specified in opts.ExcludeLabels.
-		// If it does, the resource is skipped.
-		if excluded, _ := HasExcludedLabel(job.Labels, filterOpts.ExcludeLabels); excluded {
-			continue
-		}
-		// checks if the resource's age (measured from its last modified time) matches the included criteria
-		// specified by the filter options.
-		if included, _ := HasIncludedAge(job.CreationTimestamp, filterOpts); !included {
+		if pass, _ := filter.Run(filterOpts); pass {
 			continue
 		}
 
@@ -43,9 +35,9 @@ func ProcessNamespaceJobs(clientset kubernetes.Interface, namespace string, filt
 	return unusedJobNames, nil
 }
 
-func GetUnusedJobs(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedJobs(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
+	namespaces := filterOpts.Namespaces(clientset)
 	response := make(map[string]map[string][]string)
 
 	for _, namespace := range namespaces {
diff --git a/pkg/kor/kor.go b/pkg/kor/kor.go
index 1ff1ee1..aead23f 100644
--- a/pkg/kor/kor.go
+++ b/pkg/kor/kor.go
@@ -2,16 +2,13 @@ package kor
 
 import (
 	"bytes"
-	"context"
 	"fmt"
 	"os"
 	"path/filepath"
 	"sort"
-	"strings"
 
 	"github.com/olekukonko/tablewriter"
 	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/dynamic"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/rest"
@@ -117,49 +114,6 @@ func GetDynamicClient(kubeconfig string) *dynamic.DynamicClient {
 	return clientset
 }
 
-func SetNamespaceList(namespaceLists IncludeExcludeLists, clientset kubernetes.Interface) []string {
-	namespaces := make([]string, 0)
-	namespacesMap := make(map[string]bool)
-	if namespaceLists.IncludeListStr != "" && namespaceLists.ExcludeListStr != "" {
-		fmt.Fprintf(os.Stderr, "Exclude namespaces can't be used together with include namespaces. Ignoring --exclude-namespace(-e) flag\n")
-		namespaceLists.ExcludeListStr = ""
-	}
-	includeNamespaces := strings.Split(namespaceLists.IncludeListStr, ",")
-	excludeNamespaces := strings.Split(namespaceLists.ExcludeListStr, ",")
-	namespaceList, err := clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Failed to retrieve namespaces: %v\n", err)
-		os.Exit(1)
-	}
-	if namespaceLists.IncludeListStr != "" {
-		for _, ns := range namespaceList.Items {
-			namespacesMap[ns.Name] = false
-		}
-		for _, ns := range includeNamespaces {
-			if _, exists := namespacesMap[ns]; exists {
-				namespacesMap[ns] = true
-			} else {
-				fmt.Fprintf(os.Stderr, "namespace [%s] not found\n", ns)
-			}
-		}
-	} else {
-		for _, ns := range namespaceList.Items {
-			namespacesMap[ns.Name] = true
-		}
-		for _, ns := range excludeNamespaces {
-			if _, exists := namespacesMap[ns]; exists {
-				namespacesMap[ns] = false
-			}
-		}
-	}
-	for ns := range namespacesMap {
-		if namespacesMap[ns] {
-			namespaces = append(namespaces, ns)
-		}
-	}
-	return namespaces
-}
-
 func FormatOutput(namespace string, resources []string, resourceType string, opts Opts) string {
 	if opts.Verbose && len(resources) == 0 {
 		return fmt.Sprintf("No unused %s found in the namespace: %s \n", resourceType, namespace)
@@ -251,7 +205,7 @@ func FormatOutputAll(namespace string, allDiffs []ResourceDiff, opts Opts) strin
 }
 
 // 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 {
 	var difference []string
 	for _, name := range allResourceNames {
diff --git a/pkg/kor/multi.go b/pkg/kor/multi.go
index b32b9e5..7c65b09 100644
--- a/pkg/kor/multi.go
+++ b/pkg/kor/multi.go
@@ -10,9 +10,11 @@ import (
 	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 	"k8s.io/client-go/dynamic"
 	"k8s.io/client-go/kubernetes"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
-func retrieveNoNamespaceDiff(clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, resourceList []string, filterOpts *FilterOptions) ([]ResourceDiff, []string) {
+func retrieveNoNamespaceDiff(clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, resourceList []string, filterOpts *filters.Options) ([]ResourceDiff, []string) {
 	var noNamespaceDiff []ResourceDiff
 	markedForRemoval := make([]bool, len(resourceList))
 	updatedResourceList := resourceList
@@ -20,7 +22,7 @@ func retrieveNoNamespaceDiff(clientset kubernetes.Interface, apiExtClient apiext
 	for counter, resource := range resourceList {
 		switch resource {
 		case "crd", "customresourcedefinition", "customresourcedefinitions":
-			crdDiff := getUnusedCrds(apiExtClient, dynamicClient)
+			crdDiff := getUnusedCrds(apiExtClient, dynamicClient, filterOpts)
 			noNamespaceDiff = append(noNamespaceDiff, crdDiff)
 			markedForRemoval[counter] = true
 		case "pv", "persistentvolume", "persistentvolumes":
@@ -41,7 +43,7 @@ func retrieveNoNamespaceDiff(clientset kubernetes.Interface, apiExtClient apiext
 	return noNamespaceDiff, clearedResourceList
 }
 
-func retrieveNamespaceDiffs(clientset kubernetes.Interface, namespace string, resourceList []string, filterOpts *FilterOptions) []ResourceDiff {
+func retrieveNamespaceDiffs(clientset kubernetes.Interface, namespace string, resourceList []string, filterOpts *filters.Options) []ResourceDiff {
 	var allDiffs []ResourceDiff
 	for _, resource := range resourceList {
 		var diffResult ResourceDiff
@@ -82,12 +84,12 @@ func retrieveNamespaceDiffs(clientset kubernetes.Interface, namespace string, re
 	return allDiffs
 }
 
-func GetUnusedMulti(includeExcludeLists IncludeExcludeLists, resourceNames string, filterOpts *FilterOptions, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedMulti(resourceNames string, filterOpts *filters.Options, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) (string, error) {
 	var allDiffs []ResourceDiff
 	var outputBuffer bytes.Buffer
 	var unusedMulti string
 	resourceList := strings.Split(resourceNames, ",")
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
+	namespaces := filterOpts.Namespaces(clientset)
 	response := make(map[string]map[string][]string)
 	var err error
 
diff --git a/pkg/kor/pdbs.go b/pkg/kor/pdbs.go
index caaf3ac..1ff9b6e 100644
--- a/pkg/kor/pdbs.go
+++ b/pkg/kor/pdbs.go
@@ -10,28 +10,19 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
 	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
-func processNamespacePdbs(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
+func processNamespacePdbs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
 	var unusedPdbs []string
-	pdbs, err := clientset.PolicyV1().PodDisruptionBudgets(namespace).List(context.TODO(), metav1.ListOptions{})
+	pdbs, err := clientset.PolicyV1().PodDisruptionBudgets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
 
 	for _, pdb := range pdbs.Items {
-		if pdb.Labels["kor/used"] == "true" {
-			continue
-		}
-
-		// checks if the resource has any labels that match the excluded selector specified in opts.ExcludeLabels.
-		// If it does, the resource is skipped.
-		if excluded, _ := HasExcludedLabel(pdb.Labels, filterOpts.ExcludeLabels); excluded {
-			continue
-		}
-		// checks if the resource's age (measured from its last modified time) matches the included criteria
-		// specified by the filter options.
-		if included, _ := HasIncludedAge(pdb.CreationTimestamp, filterOpts); !included {
+		if pass, _ := filter.Run(filterOpts); pass {
 			continue
 		}
 
@@ -59,9 +50,9 @@ func processNamespacePdbs(clientset kubernetes.Interface, namespace string, filt
 	return unusedPdbs, nil
 }
 
-func GetUnusedPdbs(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedPdbs(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
+	namespaces := filterOpts.Namespaces(clientset)
 	response := make(map[string]map[string][]string)
 
 	for _, namespace := range namespaces {
diff --git a/pkg/kor/pods.go b/pkg/kor/pods.go
index aec27bf..66769e0 100644
--- a/pkg/kor/pods.go
+++ b/pkg/kor/pods.go
@@ -10,10 +10,12 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
-func ProcessNamespacePods(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
-	podsList, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
+func ProcessNamespacePods(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+	podsList, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
@@ -21,18 +23,7 @@ func ProcessNamespacePods(clientset kubernetes.Interface, namespace string, filt
 	var evictedPods []string
 
 	for _, pod := range podsList.Items {
-		if pod.Labels["kor/used"] == "true" {
-			continue
-		}
-
-		// checks if the resource has any labels that match the excluded selector specified in opts.ExcludeLabels.
-		// If it does, the resource is skipped.
-		if excluded, _ := HasExcludedLabel(pod.Labels, filterOpts.ExcludeLabels); excluded {
-			continue
-		}
-		// checks if the resource's age (measured from its last modified time) matches the included criteria
-		// specified by the filter options.
-		if included, _ := HasIncludedAge(pod.CreationTimestamp, filterOpts); !included {
+		if pass, _ := filter.SetObject(&pod).Run(filterOpts); pass {
 			continue
 		}
 
@@ -45,9 +36,9 @@ func ProcessNamespacePods(clientset kubernetes.Interface, namespace string, filt
 	return evictedPods, nil
 }
 
-func GetUnusedPods(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedPods(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
+	namespaces := filterOpts.Namespaces(clientset)
 	response := make(map[string]map[string][]string)
 
 	for _, namespace := range namespaces {
diff --git a/pkg/kor/pv.go b/pkg/kor/pv.go
index 7f4fbd6..bd2d289 100644
--- a/pkg/kor/pv.go
+++ b/pkg/kor/pv.go
@@ -10,10 +10,12 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
 	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
-func processPvs(clientset kubernetes.Interface, filterOpts *FilterOptions) ([]string, error) {
-	pvs, err := clientset.CoreV1().PersistentVolumes().List(context.TODO(), metav1.ListOptions{})
+func processPvs(clientset kubernetes.Interface, filterOpts *filters.Options) ([]string, error) {
+	pvs, err := clientset.CoreV1().PersistentVolumes().List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
@@ -21,15 +23,7 @@ func processPvs(clientset kubernetes.Interface, filterOpts *FilterOptions) ([]st
 	var unusedPvs []string
 
 	for _, pv := range pvs.Items {
-		if pv.Labels["kor/used"] == "true" {
-			continue
-		}
-
-		if excluded, _ := HasExcludedLabel(pv.Labels, filterOpts.ExcludeLabels); excluded {
-			continue
-		}
-
-		if included, _ := HasIncludedAge(pv.CreationTimestamp, filterOpts); !included {
+		if pass, _ := filter.Run(filterOpts); pass {
 			continue
 		}
 
@@ -43,7 +37,7 @@ func processPvs(clientset kubernetes.Interface, filterOpts *FilterOptions) ([]st
 
 }
 
-func GetUnusedPvs(filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedPvs(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
 	response := make(map[string]map[string][]string)
 
diff --git a/pkg/kor/pvc.go b/pkg/kor/pvc.go
index a35e71b..2df29f3 100644
--- a/pkg/kor/pvc.go
+++ b/pkg/kor/pvc.go
@@ -10,9 +10,11 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
 	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
-func retreiveUsedPvcs(clientset kubernetes.Interface, namespace string) ([]string, error) {
+func retrieveUsedPvcs(clientset kubernetes.Interface, namespace string) ([]string, error) {
 	pods, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
 	if err != nil {
 		fmt.Printf("Failed to list Pods: %v\n", err)
@@ -30,32 +32,21 @@ func retreiveUsedPvcs(clientset kubernetes.Interface, namespace string) ([]strin
 	return usedPvcs, err
 }
 
-func processNamespacePvcs(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
-	pvcs, err := clientset.CoreV1().PersistentVolumeClaims(namespace).List(context.TODO(), metav1.ListOptions{})
+func processNamespacePvcs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+	pvcs, err := clientset.CoreV1().PersistentVolumeClaims(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
 	pvcNames := make([]string, 0, len(pvcs.Items))
 	for _, pvc := range pvcs.Items {
-		if pvc.Labels["kor/used"] == "true" {
-			continue
-		}
-
-		// checks if the resource has any labels that match the excluded selector specified in opts.ExcludeLabels.
-		// If it does, the resource is skipped.
-		if excluded, _ := HasExcludedLabel(pvc.Labels, filterOpts.ExcludeLabels); excluded {
-			continue
-		}
-		// checks if the resource's age (measured from its last modified time) matches the included criteria
-		// specified by the filter options.
-		if included, _ := HasIncludedAge(pvc.CreationTimestamp, filterOpts); !included {
+		if pass, _ := filter.Run(filterOpts); pass {
 			continue
 		}
 
 		pvcNames = append(pvcNames, pvc.Name)
 	}
 
-	usedPvcs, err := retreiveUsedPvcs(clientset, namespace)
+	usedPvcs, err := retrieveUsedPvcs(clientset, namespace)
 	if err != nil {
 		return nil, err
 	}
@@ -64,9 +55,9 @@ func processNamespacePvcs(clientset kubernetes.Interface, namespace string, filt
 	return diff, nil
 }
 
-func GetUnusedPvcs(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedPvcs(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
+	namespaces := filterOpts.Namespaces(clientset)
 	response := make(map[string]map[string][]string)
 
 	for _, namespace := range namespaces {
diff --git a/pkg/kor/replicaset.go b/pkg/kor/replicaset.go
index 90247b6..63798d4 100644
--- a/pkg/kor/replicaset.go
+++ b/pkg/kor/replicaset.go
@@ -5,13 +5,16 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"os"
+
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
-	"os"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
-func ProcessNamespaceReplicaSets(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
-	replicaSetList, err := clientset.AppsV1().ReplicaSets(namespace).List(context.TODO(), metav1.ListOptions{})
+func ProcessNamespaceReplicaSets(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+	replicaSetList, err := clientset.AppsV1().ReplicaSets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
@@ -19,18 +22,7 @@ func ProcessNamespaceReplicaSets(clientset kubernetes.Interface, namespace strin
 	var unusedReplicaSetNames []string
 
 	for _, replicaSet := range replicaSetList.Items {
-		if replicaSet.Labels["kor/used"] == "true" {
-			continue
-		}
-
-		// checks if the resource has any labels that match the excluded selector specified in opts.ExcludeLabels.
-		// If it does, the resource is skipped.
-		if excluded, _ := HasExcludedLabel(replicaSet.Labels, filterOpts.ExcludeLabels); excluded {
-			continue
-		}
-		// checks if the resource's age (measured from its last modified time) matches the included criteria
-		// specified by the filter options.
-		if included, _ := HasIncludedAge(replicaSet.CreationTimestamp, filterOpts); !included {
+		if pass, _ := filter.Run(filterOpts); pass {
 			continue
 		}
 
@@ -43,9 +35,9 @@ func ProcessNamespaceReplicaSets(clientset kubernetes.Interface, namespace strin
 	return unusedReplicaSetNames, nil
 }
 
-func GetUnusedReplicaSets(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedReplicaSets(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
+	namespaces := filterOpts.Namespaces(clientset)
 	response := make(map[string]map[string][]string)
 
 	for _, namespace := range namespaces {
diff --git a/pkg/kor/roles.go b/pkg/kor/roles.go
index 1d01cb9..14ddc9e 100644
--- a/pkg/kor/roles.go
+++ b/pkg/kor/roles.go
@@ -10,9 +10,11 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
 	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
-func retrieveUsedRoles(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
+func retrieveUsedRoles(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
 	// Get a list of all role bindings in the specified namespace
 	roleBindings, err := clientset.RbacV1().RoleBindings(namespace).List(context.TODO(), metav1.ListOptions{})
 	if err != nil {
@@ -21,14 +23,7 @@ func retrieveUsedRoles(clientset kubernetes.Interface, namespace string, filterO
 
 	usedRoles := make(map[string]bool)
 	for _, rb := range roleBindings.Items {
-		// checks if the resource has any labels that match the excluded selector specified in opts.ExcludeLabels.
-		// If it does, the resource is skipped.
-		if excluded, _ := HasExcludedLabel(rb.Labels, filterOpts.ExcludeLabels); excluded {
-			continue
-		}
-		// checks if the resource's age (measured from its last modified time) matches the included criteria
-		// specified by the filter options.
-		if included, _ := HasIncludedAge(rb.CreationTimestamp, filterOpts); !included {
+		if pass, _ := filter.Run(filterOpts); pass {
 			continue
 		}
 
@@ -43,14 +38,14 @@ func retrieveUsedRoles(clientset kubernetes.Interface, namespace string, filterO
 	return usedRoleNames, nil
 }
 
-func retrieveRoleNames(clientset kubernetes.Interface, namespace string) ([]string, error) {
-	roles, err := clientset.RbacV1().Roles(namespace).List(context.TODO(), metav1.ListOptions{})
+func retrieveRoleNames(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+	roles, err := clientset.RbacV1().Roles(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
 	names := make([]string, 0, len(roles.Items))
 	for _, role := range roles.Items {
-		if role.Labels["kor/used"] == "true" {
+		if pass := filters.KorLabelFilter(&role, &filters.Options{}); pass {
 			continue
 		}
 
@@ -59,7 +54,7 @@ func retrieveRoleNames(clientset kubernetes.Interface, namespace string) ([]stri
 	return names, nil
 }
 
-func processNamespaceRoles(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
+func processNamespaceRoles(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
 	usedRoles, err := retrieveUsedRoles(clientset, namespace, filterOpts)
 	if err != nil {
 		return nil, err
@@ -67,7 +62,7 @@ func processNamespaceRoles(clientset kubernetes.Interface, namespace string, fil
 
 	usedRoles = RemoveDuplicatesAndSort(usedRoles)
 
-	roleNames, err := retrieveRoleNames(clientset, namespace)
+	roleNames, err := retrieveRoleNames(clientset, namespace, filterOpts)
 	if err != nil {
 		return nil, err
 	}
@@ -77,9 +72,9 @@ func processNamespaceRoles(clientset kubernetes.Interface, namespace string, fil
 
 }
 
-func GetUnusedRoles(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedRoles(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
+	namespaces := filterOpts.Namespaces(clientset)
 	response := make(map[string]map[string][]string)
 
 	for _, namespace := range namespaces {
diff --git a/pkg/kor/secrets.go b/pkg/kor/secrets.go
index cef2fb9..eb3c4ad 100644
--- a/pkg/kor/secrets.go
+++ b/pkg/kor/secrets.go
@@ -11,6 +11,8 @@ import (
 	"k8s.io/client-go/kubernetes"
 	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
 	"k8s.io/utils/strings/slices"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 var exceptionSecretTypes = []string{
@@ -101,25 +103,14 @@ func retrieveUsedSecret(clientset kubernetes.Interface, namespace string) ([]str
 	return envSecrets, envSecrets2, volumeSecrets, initContainerEnvSecrets, pullSecrets, tlsSecrets, nil
 }
 
-func retrieveSecretNames(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
-	secrets, err := clientset.CoreV1().Secrets(namespace).List(context.TODO(), metav1.ListOptions{})
+func retrieveSecretNames(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+	secrets, err := clientset.CoreV1().Secrets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
 	names := make([]string, 0, len(secrets.Items))
 	for _, secret := range secrets.Items {
-		if secret.Labels["kor/used"] == "true" {
-			continue
-		}
-
-		// checks if the resource has any labels that match the excluded selector specified in opts.ExcludeLabels.
-		// If it does, the resource is skipped.
-		if excluded, _ := HasExcludedLabel(secret.Labels, filterOpts.ExcludeLabels); excluded {
-			continue
-		}
-		// checks if the resource's age (measured from its last modified time) matches the included criteria
-		// specified by the filter options.
-		if included, _ := HasIncludedAge(secret.CreationTimestamp, filterOpts); !included {
+		if pass, _ := filter.Run(filterOpts); pass {
 			continue
 		}
 
@@ -130,7 +121,7 @@ func retrieveSecretNames(clientset kubernetes.Interface, namespace string, filte
 	return names, nil
 }
 
-func processNamespaceSecret(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
+func processNamespaceSecret(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
 	envSecrets, envSecrets2, volumeSecrets, initContainerEnvSecrets, pullSecrets, tlsSecrets, err := retrieveUsedSecret(clientset, namespace)
 	if err != nil {
 		return nil, err
@@ -159,9 +150,9 @@ func processNamespaceSecret(clientset kubernetes.Interface, namespace string, fi
 
 }
 
-func GetUnusedSecrets(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedSecrets(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
+	namespaces := filterOpts.Namespaces(clientset)
 	response := make(map[string]map[string][]string)
 
 	for _, namespace := range namespaces {
diff --git a/pkg/kor/serviceaccounts.go b/pkg/kor/serviceaccounts.go
index 5113378..cd18824 100644
--- a/pkg/kor/serviceaccounts.go
+++ b/pkg/kor/serviceaccounts.go
@@ -9,6 +9,8 @@ import (
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 var exceptionServiceAccounts = []ExceptionResource{
@@ -27,7 +29,7 @@ func getServiceAccountsFromClusterRoleBindings(clientset kubernetes.Interface, n
 
 	// Extract service account names from the role bindings
 	for _, rb := range roleBindings.Items {
-		if rb.Labels["kor/used"] == "true" {
+		if pass := filters.KorLabelFilter(&rb, &filters.Options{}); pass {
 			continue
 		}
 
@@ -101,22 +103,14 @@ func retrieveUsedSA(clientset kubernetes.Interface, namespace string) ([]string,
 	return podServiceAccounts, roleServiceAccounts, clusterRoleServiceAccounts, nil
 }
 
-func retrieveServiceAccountNames(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
-	serviceaccounts, err := clientset.CoreV1().ServiceAccounts(namespace).List(context.TODO(), metav1.ListOptions{})
+func retrieveServiceAccountNames(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+	serviceaccounts, err := clientset.CoreV1().ServiceAccounts(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
 	names := make([]string, 0, len(serviceaccounts.Items))
 	for _, serviceaccount := range serviceaccounts.Items {
-		if serviceaccount.Labels["kor/used"] == "true" {
-			continue
-		}
-
-		if excluded, _ := HasExcludedLabel(serviceaccount.Labels, filterOpts.ExcludeLabels); excluded {
-			continue
-		}
-
-		if included, _ := HasIncludedAge(serviceaccount.CreationTimestamp, filterOpts); !included {
+		if pass, _ := filter.SetObject(&serviceaccount).Run(filterOpts); pass {
 			continue
 		}
 
@@ -125,7 +119,7 @@ func retrieveServiceAccountNames(clientset kubernetes.Interface, namespace strin
 	return names, nil
 }
 
-func processNamespaceSA(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
+func processNamespaceSA(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
 	usedServiceAccounts, roleServiceAccounts, clusterRoleServiceAccounts, err := retrieveUsedSA(clientset, namespace)
 	if err != nil {
 		return nil, err
@@ -147,10 +141,10 @@ func processNamespaceSA(clientset kubernetes.Interface, namespace string, filter
 
 }
 
-func GetUnusedServiceAccounts(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedServiceAccounts(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
 
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
+	namespaces := filterOpts.Namespaces(clientset)
 	response := make(map[string]map[string][]string)
 
 	for _, namespace := range namespaces {
diff --git a/pkg/kor/services.go b/pkg/kor/services.go
index 88ae461..139fadc 100644
--- a/pkg/kor/services.go
+++ b/pkg/kor/services.go
@@ -9,10 +9,12 @@ import (
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
-func ProcessNamespaceServices(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
-	endpointsList, err := clientset.CoreV1().Endpoints(namespace).List(context.TODO(), metav1.ListOptions{})
+func ProcessNamespaceServices(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+	endpointsList, err := clientset.CoreV1().Endpoints(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
@@ -20,15 +22,7 @@ func ProcessNamespaceServices(clientset kubernetes.Interface, namespace string,
 	var endpointsWithoutSubsets []string
 
 	for _, endpoints := range endpointsList.Items {
-		if endpoints.Labels["kor/used"] == "true" {
-			continue
-		}
-
-		if excluded, _ := HasExcludedLabel(endpoints.Labels, filterOpts.ExcludeLabels); excluded {
-			continue
-		}
-
-		if included, _ := HasIncludedAge(endpoints.CreationTimestamp, filterOpts); !included {
+		if pass, _ := filter.Run(filterOpts); pass {
 			continue
 		}
 
@@ -40,10 +34,10 @@ func ProcessNamespaceServices(clientset kubernetes.Interface, namespace string,
 	return endpointsWithoutSubsets, nil
 }
 
-func GetUnusedServices(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedServices(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
 
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
+	namespaces := filterOpts.Namespaces(clientset)
 	response := make(map[string]map[string][]string)
 
 	for _, namespace := range namespaces {
diff --git a/pkg/kor/statefulsets.go b/pkg/kor/statefulsets.go
index 3b2b1bf..dac7413 100644
--- a/pkg/kor/statefulsets.go
+++ b/pkg/kor/statefulsets.go
@@ -9,10 +9,12 @@ import (
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
-func ProcessNamespaceStatefulSets(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) {
-	statefulSetsList, err := clientset.AppsV1().StatefulSets(namespace).List(context.TODO(), metav1.ListOptions{})
+func ProcessNamespaceStatefulSets(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) {
+	statefulSetsList, err := clientset.AppsV1().StatefulSets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
 	if err != nil {
 		return nil, err
 	}
@@ -20,14 +22,7 @@ func ProcessNamespaceStatefulSets(clientset kubernetes.Interface, namespace stri
 	var statefulSetsWithoutReplicas []string
 
 	for _, statefulSet := range statefulSetsList.Items {
-		// checks if the resource has any labels that match the excluded selector specified in opts.ExcludeLabels.
-		// If it does, the resource is skipped.
-		if excluded, _ := HasExcludedLabel(statefulSet.Labels, filterOpts.ExcludeLabels); excluded {
-			continue
-		}
-		// checks if the resource's age (measured from its last modified time) matches the included criteria
-		// specified by the filter options.
-		if included, _ := HasIncludedAge(statefulSet.CreationTimestamp, filterOpts); !included {
+		if pass, _ := filter.Run(filterOpts); pass {
 			continue
 		}
 
@@ -39,9 +34,9 @@ func ProcessNamespaceStatefulSets(clientset kubernetes.Interface, namespace stri
 	return statefulSetsWithoutReplicas, nil
 }
 
-func GetUnusedStatefulSets(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+func GetUnusedStatefulSets(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
-	namespaces := SetNamespaceList(includeExcludeLists, clientset)
+	namespaces := filterOpts.Namespaces(clientset)
 	response := make(map[string]map[string][]string)
 
 	for _, namespace := range namespaces {


Test patch:
diff --git a/pkg/filters/filters_test.go b/pkg/filters/filters_test.go
new file mode 100644
index 0000000..4e3ae03
--- /dev/null
+++ b/pkg/filters/filters_test.go
@@ -0,0 +1,179 @@
+package filters
+
+import (
+	"testing"
+	"time"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+func TestLabelFilter(t *testing.T) {
+	type args struct {
+		object runtime.Object
+		opts   *Options
+	}
+	node := &corev1.Node{
+		ObjectMeta: metav1.ObjectMeta{
+			Labels: map[string]string{
+				"foo": "bar",
+				"app": "test",
+			},
+		},
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "don't have exclude label",
+			args: args{
+				object: node,
+				opts: &Options{
+					ExcludeLabels: "foo=barbar",
+				},
+			},
+			want: false,
+		},
+		{
+			name: "have exclude label",
+			args: args{
+				object: node,
+				opts: &Options{
+					ExcludeLabels: "foo=bar",
+				},
+			},
+			want: true,
+		},
+	}
+	for _, tt := range tests {
+		got := LabelFilter(tt.args.object, tt.args.opts)
+		if got != tt.want {
+			t.Errorf("%s LabelFilter() = %v, want %v", tt.name, got, tt.want)
+		}
+	}
+}
+
+func TestAgeFilter(t *testing.T) {
+	type args struct {
+		object runtime.Object
+		opts   *Options
+	}
+	now := time.Now()
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "not older than 3h",
+			args: args{
+				object: &corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						CreationTimestamp: metav1.Time{Time: now},
+					},
+				},
+				opts: &Options{
+					NewerThan: "3h",
+				},
+			},
+			want: false,
+		},
+		{
+			name: "more than 3h",
+			args: args{
+				object: &corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						CreationTimestamp: metav1.Time{Time: now.Add(-4 * time.Hour)},
+					},
+				},
+				opts: &Options{
+					OlderThan: "3h",
+				},
+			},
+			want: false,
+		},
+		{
+			name: "two flags are provided",
+			args: args{
+				object: &corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						CreationTimestamp: metav1.Time{Time: now.Add(-4 * time.Hour)},
+					},
+				},
+				opts: &Options{
+					NewerThan: "3h",
+					OlderThan: "2h",
+				},
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		got := AgeFilter(tt.args.object, tt.args.opts)
+		if got != tt.want {
+			t.Errorf("%s HasIncludedAge() = %v, want %v", tt.name, got, tt.want)
+		}
+	}
+}
+
+func TestKorLabelFilter(t *testing.T) {
+	type args struct {
+		object runtime.Object
+		opts   *Options
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "don't have kor/used label",
+			args: args{
+				object: &corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Labels: map[string]string{
+							"foo": "bar",
+						},
+					},
+				},
+			},
+			want: false,
+		},
+		{
+			name: "have kor/used label true",
+			args: args{
+				object: &corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Labels: map[string]string{
+							"kor/used": "true",
+						},
+					},
+				},
+			},
+			want: true,
+		},
+		{
+			name: "have kor/used label false",
+			args: args{
+				object: &corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Labels: map[string]string{
+							"kor/used": "false",
+						},
+					},
+				},
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := KorLabelFilter(tt.args.object, tt.args.opts); got != tt.want {
+				t.Errorf("KorLabelFilter() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/pkg/kor/configmaps_test.go b/pkg/kor/configmaps_test.go
index 6930b22..c0063ec 100644
--- a/pkg/kor/configmaps_test.go
+++ b/pkg/kor/configmaps_test.go
@@ -12,6 +12,8 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/kubernetes/scheme"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func createTestConfigmaps(t *testing.T) *fake.Clientset {
@@ -101,7 +103,7 @@ func createTestConfigmaps(t *testing.T) *fake.Clientset {
 func TestRetrieveConfigMapNames(t *testing.T) {
 	clientset := createTestConfigmaps(t)
 
-	configMapNames, err := retrieveConfigMapNames(clientset, testNamespace, &FilterOptions{})
+	configMapNames, err := retrieveConfigMapNames(clientset, testNamespace, &filters.Options{})
 
 	if err != nil {
 		t.Fatalf("Error retrieving configmap names: %v", err)
@@ -116,7 +118,7 @@ func TestRetrieveConfigMapNames(t *testing.T) {
 func TestProcessNamespaceCM(t *testing.T) {
 	clientset := createTestConfigmaps(t)
 
-	diff, err := processNamespaceCM(clientset, testNamespace, &FilterOptions{})
+	diff, err := processNamespaceCM(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Fatalf("Error processing namespace CM: %v", err)
 	}
@@ -166,11 +168,6 @@ func TestRetrieveUsedCM(t *testing.T) {
 func TestGetUnusedConfigmapsStructured(t *testing.T) {
 	clientset := createTestConfigmaps(t)
 
-	includeExcludeLists := IncludeExcludeLists{
-		IncludeListStr: "",
-		ExcludeListStr: "",
-	}
-
 	opts := Opts{
 		WebhookURL:    "",
 		Channel:       "",
@@ -179,7 +176,7 @@ func TestGetUnusedConfigmapsStructured(t *testing.T) {
 		NoInteractive: true,
 	}
 
-	output, err := GetUnusedConfigmaps(includeExcludeLists, &FilterOptions{}, clientset, "json", opts)
+	output, err := GetUnusedConfigmaps(&filters.Options{}, clientset, "json", opts)
 	if err != nil {
 		t.Fatalf("Error calling GetUnusedConfigmapsStructured: %v", err)
 	}
diff --git a/pkg/kor/deployments_test.go b/pkg/kor/deployments_test.go
index fae808f..74af513 100644
--- a/pkg/kor/deployments_test.go
+++ b/pkg/kor/deployments_test.go
@@ -12,6 +12,8 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/kubernetes/scheme"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func createTestDeployments(t *testing.T) *fake.Clientset {
@@ -45,7 +47,7 @@ func createTestDeployments(t *testing.T) *fake.Clientset {
 func TestProcessNamespaceDeployments(t *testing.T) {
 	clientset := createTestDeployments(t)
 
-	deploymentsWithoutReplicas, err := ProcessNamespaceDeployments(clientset, testNamespace, &FilterOptions{})
+	deploymentsWithoutReplicas, err := ProcessNamespaceDeployments(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
 	}
@@ -62,11 +64,6 @@ func TestProcessNamespaceDeployments(t *testing.T) {
 func TestGetUnusedDeploymentsStructured(t *testing.T) {
 	clientset := createTestDeployments(t)
 
-	includeExcludeLists := IncludeExcludeLists{
-		IncludeListStr: "",
-		ExcludeListStr: "",
-	}
-
 	opts := Opts{
 		WebhookURL:    "",
 		Channel:       "",
@@ -75,7 +72,7 @@ func TestGetUnusedDeploymentsStructured(t *testing.T) {
 		NoInteractive: true,
 	}
 
-	output, err := GetUnusedDeployments(includeExcludeLists, &FilterOptions{}, clientset, "json", opts)
+	output, err := GetUnusedDeployments(&filters.Options{}, clientset, "json", opts)
 	if err != nil {
 		t.Fatalf("Error calling GetUnusedDeploymentsStructured: %v", err)
 	}
diff --git a/pkg/kor/filter_options_test.go b/pkg/kor/filter_options_test.go
deleted file mode 100644
index eec29f5..0000000
--- a/pkg/kor/filter_options_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package kor
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/assert"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
-func TestHasIncludedAge(t *testing.T) {
-	tests := []struct {
-		name         string
-		creationTime time.Time // resource creation time
-		opts         *FilterOptions
-		want         bool
-	}{
-		{
-			name:         "The resource is not older than 20 minutes",
-			creationTime: metav1.Now().Time,
-			opts:         &FilterOptions{NewerThan: "20m"},
-			want:         true,
-		}, {
-			name:         "The resource age is more than 10 second",
-			creationTime: metav1.Now().Add(-12 * time.Second),
-			opts:         &FilterOptions{OlderThan: "10s"},
-			want:         true,
-		}, {
-			name:         "Two flags are provided",
-			creationTime: metav1.Now().Time,
-			opts:         &FilterOptions{OlderThan: "20m", NewerThan: "10m"},
-			want:         false,
-		},
-	}
-
-	for _, tt := range tests {
-		got, _ := HasIncludedAge(
-			metav1.NewTime(tt.creationTime),
-			tt.opts,
-		)
-		assert.Equal(t, tt.want, got)
-	}
-
-}
-
-func TestHasExcludedLabel(t *testing.T) {
-	tests := []struct {
-		resourcelabels  map[string]string
-		excludeSelector string
-		want            bool
-	}{
-		{
-			resourcelabels:  map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"},
-			excludeSelector: "key2=val2",
-			want:            true,
-		},
-		{
-			resourcelabels:  map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"},
-			excludeSelector: "",
-			want:            false,
-		},
-		{
-			resourcelabels:  map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"},
-			excludeSelector: "key4=val1",
-			want:            false,
-		},
-		{
-			resourcelabels:  map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"},
-			excludeSelector: "key1=val5",
-			want:            false,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run("", func(t *testing.T) {
-			got, err := HasExcludedLabel(tt.resourcelabels, tt.excludeSelector)
-			assert.NoError(t, err)
-			assert.Equal(t, tt.want, got)
-		})
-	}
-}
diff --git a/pkg/kor/finalizers_test.go b/pkg/kor/finalizers_test.go
index 7e18f65..c872105 100644
--- a/pkg/kor/finalizers_test.go
+++ b/pkg/kor/finalizers_test.go
@@ -9,6 +9,8 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	fakedynamic "k8s.io/client-go/dynamic/fake"
 	"k8s.io/utils/strings/slices"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func TestCheckFinalizers(t *testing.T) {
@@ -85,7 +87,7 @@ func TestRetrievePendingDeletionResources(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			result, err := retrievePendingDeletionResources(test.apiResourceLists, dynamicClient, &FilterOptions{})
+			result, err := retrievePendingDeletionResources(test.apiResourceLists, dynamicClient, &filters.Options{})
 			if (err != nil) != test.expectedError {
 				t.Errorf("Expected error: %v, Got: %v", test.expectedError, err)
 			}
diff --git a/pkg/kor/hpas_test.go b/pkg/kor/hpas_test.go
index 14ec551..622e3e4 100644
--- a/pkg/kor/hpas_test.go
+++ b/pkg/kor/hpas_test.go
@@ -12,6 +12,8 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/kubernetes/scheme"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func createTestHpas(t *testing.T) *fake.Clientset {
@@ -53,7 +55,7 @@ func createTestHpas(t *testing.T) *fake.Clientset {
 func TestExtractUnusedHpas(t *testing.T) {
 	clientset := createTestHpas(t)
 
-	unusedHpas, err := extractUnusedHpas(clientset, testNamespace, &FilterOptions{})
+	unusedHpas, err := extractUnusedHpas(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
 	}
@@ -70,11 +72,6 @@ func TestExtractUnusedHpas(t *testing.T) {
 func TestGetUnusedHpasStructured(t *testing.T) {
 	clientset := createTestHpas(t)
 
-	includeExcludeLists := IncludeExcludeLists{
-		IncludeListStr: "",
-		ExcludeListStr: "",
-	}
-
 	opts := Opts{
 		WebhookURL:    "",
 		Channel:       "",
@@ -83,7 +80,7 @@ func TestGetUnusedHpasStructured(t *testing.T) {
 		NoInteractive: true,
 	}
 
-	output, err := GetUnusedHpas(includeExcludeLists, &FilterOptions{}, clientset, "json", opts)
+	output, err := GetUnusedHpas(&filters.Options{}, clientset, "json", opts)
 	if err != nil {
 		t.Fatalf("Error calling GetUnusedHpasStructured: %v", err)
 	}
diff --git a/pkg/kor/ingresses_test.go b/pkg/kor/ingresses_test.go
index 39e8fdb..e647978 100644
--- a/pkg/kor/ingresses_test.go
+++ b/pkg/kor/ingresses_test.go
@@ -12,6 +12,8 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/kubernetes/scheme"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func createTestIngresses(t *testing.T) *fake.Clientset {
@@ -48,7 +50,7 @@ func createTestIngresses(t *testing.T) *fake.Clientset {
 func TestRetrieveUsedIngress(t *testing.T) {
 	clientset := createTestIngresses(t)
 
-	usedIngresses, err := retrieveUsedIngress(clientset, testNamespace, &FilterOptions{})
+	usedIngresses, err := retrieveUsedIngress(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
 	}
@@ -74,11 +76,6 @@ func contains(slice []string, item string) bool {
 func TestGetUnusedIngressesStructured(t *testing.T) {
 	clientset := createTestIngresses(t)
 
-	includeExcludeLists := IncludeExcludeLists{
-		IncludeListStr: "",
-		ExcludeListStr: "",
-	}
-
 	opts := Opts{
 		WebhookURL:    "",
 		Channel:       "",
@@ -87,7 +84,7 @@ func TestGetUnusedIngressesStructured(t *testing.T) {
 		NoInteractive: true,
 	}
 
-	output, err := GetUnusedIngresses(includeExcludeLists, &FilterOptions{}, clientset, "json", opts)
+	output, err := GetUnusedIngresses(&filters.Options{}, clientset, "json", opts)
 	if err != nil {
 		t.Fatalf("Error calling GetUnusedIngressesStructured: %v", err)
 	}
diff --git a/pkg/kor/jobs_test.go b/pkg/kor/jobs_test.go
index d6498bf..f39f3e8 100644
--- a/pkg/kor/jobs_test.go
+++ b/pkg/kor/jobs_test.go
@@ -3,6 +3,10 @@ package kor
 import (
 	"context"
 	"encoding/json"
+	"reflect"
+	"testing"
+	"time"
+
 	appsv1 "k8s.io/api/apps/v1"
 	batchv1 "k8s.io/api/batch/v1"
 	corev1 "k8s.io/api/core/v1"
@@ -10,9 +14,8 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/kubernetes/scheme"
-	"reflect"
-	"testing"
-	"time"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func createTestJobs(t *testing.T) *fake.Clientset {
@@ -51,7 +54,7 @@ func createTestJobs(t *testing.T) *fake.Clientset {
 func TestProcessNamespaceJobs(t *testing.T) {
 	clientset := createTestJobs(t)
 
-	completedJobs, err := ProcessNamespaceJobs(clientset, testNamespace, &FilterOptions{})
+	completedJobs, err := ProcessNamespaceJobs(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
 	}
@@ -68,11 +71,6 @@ func TestProcessNamespaceJobs(t *testing.T) {
 func TestGetUnusedJobsStructured(t *testing.T) {
 	clientset := createTestJobs(t)
 
-	includeExcludeLists := IncludeExcludeLists{
-		IncludeListStr: "",
-		ExcludeListStr: "",
-	}
-
 	opts := Opts{
 		WebhookURL:    "",
 		Channel:       "",
@@ -81,7 +79,7 @@ func TestGetUnusedJobsStructured(t *testing.T) {
 		NoInteractive: true,
 	}
 
-	output, err := GetUnusedJobs(includeExcludeLists, &FilterOptions{}, clientset, "json", opts)
+	output, err := GetUnusedJobs(&filters.Options{}, clientset, "json", opts)
 	if err != nil {
 		t.Fatalf("Error calling GetUnusedJobsStructured: %v", err)
 	}
diff --git a/pkg/kor/pdbs_test.go b/pkg/kor/pdbs_test.go
index 785aacc..2dfb556 100644
--- a/pkg/kor/pdbs_test.go
+++ b/pkg/kor/pdbs_test.go
@@ -9,6 +9,8 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes/fake"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func createTestPdbs(t *testing.T) *fake.Clientset {
@@ -63,7 +65,7 @@ func createTestPdbs(t *testing.T) *fake.Clientset {
 func TestProcessNamespacePdbs(t *testing.T) {
 	clientset := createTestPdbs(t)
 
-	unusedPdbs, err := processNamespacePdbs(clientset, testNamespace, &FilterOptions{})
+	unusedPdbs, err := processNamespacePdbs(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
 	}
@@ -80,11 +82,6 @@ func TestProcessNamespacePdbs(t *testing.T) {
 func TestGetUnusedPdbsStructured(t *testing.T) {
 	clientset := createTestPdbs(t)
 
-	includeExcludeLists := IncludeExcludeLists{
-		IncludeListStr: "",
-		ExcludeListStr: "",
-	}
-
 	opts := Opts{
 		WebhookURL:    "",
 		Channel:       "",
@@ -93,7 +90,7 @@ func TestGetUnusedPdbsStructured(t *testing.T) {
 		NoInteractive: true,
 	}
 
-	output, err := GetUnusedPdbs(includeExcludeLists, &FilterOptions{}, clientset, "json", opts)
+	output, err := GetUnusedPdbs(&filters.Options{}, clientset, "json", opts)
 	if err != nil {
 		t.Fatalf("Error calling GetUnusedPdbsStructured: %v", err)
 	}
diff --git a/pkg/kor/pods_test.go b/pkg/kor/pods_test.go
index 0fc0cb7..2107db2 100644
--- a/pkg/kor/pods_test.go
+++ b/pkg/kor/pods_test.go
@@ -12,6 +12,8 @@ import (
 
 	fake "k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/kubernetes/scheme"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func createTestPods(t *testing.T) *fake.Clientset {
@@ -83,7 +85,7 @@ func createTestPods(t *testing.T) *fake.Clientset {
 
 func TestProcessNamespacePods(t *testing.T) {
 	clientset := createTestPods(t)
-	evictedPods, err := ProcessNamespacePods(clientset, testNamespace, &FilterOptions{})
+	evictedPods, err := ProcessNamespacePods(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Errorf("Unexpected error: %v", err)
 	}
@@ -104,11 +106,6 @@ func TestProcessNamespacePods(t *testing.T) {
 func TestGetUnusedPodsStructured(t *testing.T) {
 	clientset := createTestPods(t)
 
-	includeExcludeLists := IncludeExcludeLists{
-		IncludeListStr: "",
-		ExcludeListStr: "",
-	}
-
 	opts := Opts{
 		WebhookURL:    "",
 		Channel:       "",
@@ -117,7 +114,7 @@ func TestGetUnusedPodsStructured(t *testing.T) {
 		NoInteractive: true,
 	}
 
-	output, err := GetUnusedPods(includeExcludeLists, &FilterOptions{}, clientset, "json", opts)
+	output, err := GetUnusedPods(&filters.Options{}, clientset, "json", opts)
 	if err != nil {
 		t.Fatalf("Error calling GetUnusedPodsStructured: %v", err)
 	}
diff --git a/pkg/kor/pv_test.go b/pkg/kor/pv_test.go
index 97f119b..9d41b2c 100644
--- a/pkg/kor/pv_test.go
+++ b/pkg/kor/pv_test.go
@@ -8,6 +8,8 @@ import (
 
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes/fake"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func createTestPvs(t *testing.T) *fake.Clientset {
@@ -30,7 +32,7 @@ func createTestPvs(t *testing.T) *fake.Clientset {
 
 func TestProcessPvs(t *testing.T) {
 	clientset := createTestPvs(t)
-	usedPvs, err := processPvs(clientset, &FilterOptions{})
+	usedPvs, err := processPvs(clientset, &filters.Options{})
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
 	}
@@ -55,7 +57,7 @@ func TestGetUnusedPvs(t *testing.T) {
 		NoInteractive: true,
 	}
 
-	output, err := GetUnusedPvs(&FilterOptions{}, clientset, "json", opts)
+	output, err := GetUnusedPvs(&filters.Options{}, clientset, "json", opts)
 	if err != nil {
 		t.Fatalf("Error calling GetUnusedPvs: %v", err)
 	}
@@ -75,5 +77,3 @@ func TestGetUnusedPvs(t *testing.T) {
 		t.Errorf("Expected output does not match actual output")
 	}
 }
-
-
diff --git a/pkg/kor/pvc_test.go b/pkg/kor/pvc_test.go
index 9b0124a..509ac4f 100644
--- a/pkg/kor/pvc_test.go
+++ b/pkg/kor/pvc_test.go
@@ -12,6 +12,8 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/kubernetes/scheme"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func createTestPvcs(t *testing.T) *fake.Clientset {
@@ -50,9 +52,9 @@ func createTestPvcs(t *testing.T) *fake.Clientset {
 	return clientset
 }
 
-func TestRetreiveUsedPvcs(t *testing.T) {
+func TestRetrieveUsedPvcs(t *testing.T) {
 	clientset := createTestPvcs(t)
-	usedPvcs, err := retreiveUsedPvcs(clientset, testNamespace)
+	usedPvcs, err := retrieveUsedPvcs(clientset, testNamespace)
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
 	}
@@ -68,7 +70,7 @@ func TestRetreiveUsedPvcs(t *testing.T) {
 
 func TestProcessNamespacePvcs(t *testing.T) {
 	clientset := createTestPvcs(t)
-	usedPvcs, err := processNamespacePvcs(clientset, testNamespace, &FilterOptions{})
+	usedPvcs, err := processNamespacePvcs(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
 	}
@@ -85,11 +87,6 @@ func TestProcessNamespacePvcs(t *testing.T) {
 func TestGetUnusedPvcsStructured(t *testing.T) {
 	clientset := createTestPvcs(t)
 
-	includeExcludeLists := IncludeExcludeLists{
-		IncludeListStr: "",
-		ExcludeListStr: "",
-	}
-
 	opts := Opts{
 		WebhookURL:    "",
 		Channel:       "",
@@ -98,7 +95,7 @@ func TestGetUnusedPvcsStructured(t *testing.T) {
 		NoInteractive: true,
 	}
 
-	output, err := GetUnusedPvcs(includeExcludeLists, &FilterOptions{}, clientset, "json", opts)
+	output, err := GetUnusedPvcs(&filters.Options{}, clientset, "json", opts)
 	if err != nil {
 		t.Fatalf("Error calling GetUnusedPvcsStructured: %v", err)
 	}
diff --git a/pkg/kor/replicaset_test.go b/pkg/kor/replicaset_test.go
index 8e8b2e1..57e6a2f 100644
--- a/pkg/kor/replicaset_test.go
+++ b/pkg/kor/replicaset_test.go
@@ -3,14 +3,17 @@ package kor
 import (
 	"context"
 	"encoding/json"
+	"reflect"
+	"testing"
+
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/kubernetes/scheme"
-	"reflect"
-	"testing"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func createTestReplicaSets(t *testing.T) *fake.Clientset {
@@ -54,11 +57,6 @@ func createTestReplicaSets(t *testing.T) *fake.Clientset {
 func TestProcessNamespaceReplicaSets(t *testing.T) {
 	clientset := createTestReplicaSets(t)
 
-	includeExcludeLists := IncludeExcludeLists{
-		IncludeListStr: "",
-		ExcludeListStr: "",
-	}
-
 	opts := Opts{
 		WebhookURL:    "",
 		Channel:       "",
@@ -67,7 +65,7 @@ func TestProcessNamespaceReplicaSets(t *testing.T) {
 		NoInteractive: true,
 	}
 
-	output, err := GetUnusedReplicaSets(includeExcludeLists, &FilterOptions{}, clientset, "json", opts)
+	output, err := GetUnusedReplicaSets(&filters.Options{}, clientset, "json", opts)
 	if err != nil {
 		t.Fatalf("Error calling GetUnusedReplicaSetsStructured: %v", err)
 	}
diff --git a/pkg/kor/roles_test.go b/pkg/kor/roles_test.go
index 3a96302..3ec1549 100644
--- a/pkg/kor/roles_test.go
+++ b/pkg/kor/roles_test.go
@@ -12,6 +12,8 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/kubernetes/scheme"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func createTestRoles(t *testing.T) *fake.Clientset {
@@ -49,7 +51,7 @@ func createTestRoles(t *testing.T) *fake.Clientset {
 func TestRetrieveUsedRoles(t *testing.T) {
 	clientset := createTestRoles(t)
 
-	usedRoles, err := retrieveUsedRoles(clientset, testNamespace, &FilterOptions{})
+	usedRoles, err := retrieveUsedRoles(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
 	}
@@ -65,7 +67,7 @@ func TestRetrieveUsedRoles(t *testing.T) {
 
 func TestRetrieveRoleNames(t *testing.T) {
 	clientset := createTestRoles(t)
-	allRoles, err := retrieveRoleNames(clientset, testNamespace)
+	allRoles, err := retrieveRoleNames(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
 	}
@@ -78,7 +80,7 @@ func TestRetrieveRoleNames(t *testing.T) {
 func TestProcessNamespaceRoles(t *testing.T) {
 	clientset := createTestRoles(t)
 
-	unusedRoles, err := processNamespaceRoles(clientset, testNamespace, &FilterOptions{})
+	unusedRoles, err := processNamespaceRoles(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
 	}
@@ -95,11 +97,6 @@ func TestProcessNamespaceRoles(t *testing.T) {
 func TestGetUnusedRolesStructured(t *testing.T) {
 	clientset := createTestRoles(t)
 
-	includeExcludeLists := IncludeExcludeLists{
-		IncludeListStr: "",
-		ExcludeListStr: "",
-	}
-
 	opts := Opts{
 		WebhookURL:    "",
 		Channel:       "",
@@ -108,7 +105,7 @@ func TestGetUnusedRolesStructured(t *testing.T) {
 		NoInteractive: true,
 	}
 
-	output, err := GetUnusedRoles(includeExcludeLists, &FilterOptions{}, clientset, "json", opts)
+	output, err := GetUnusedRoles(&filters.Options{}, clientset, "json", opts)
 	if err != nil {
 		t.Fatalf("Error calling GetUnusedRolesStructured: %v", err)
 	}
diff --git a/pkg/kor/secrets_test.go b/pkg/kor/secrets_test.go
index 1814ba4..eb37af2 100644
--- a/pkg/kor/secrets_test.go
+++ b/pkg/kor/secrets_test.go
@@ -12,6 +12,8 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/kubernetes/scheme"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func createTestSecrets(t *testing.T) *fake.Clientset {
@@ -206,7 +208,7 @@ func TestRetrieveSecretNames(t *testing.T) {
 		t.Fatalf("Error creating fake secret: %v", err)
 	}
 
-	secretNames, err := retrieveSecretNames(clientset, testNamespace, &FilterOptions{})
+	secretNames, err := retrieveSecretNames(clientset, testNamespace, &filters.Options{})
 
 	if err != nil {
 		t.Fatalf("Error retrieving secret names: %v", err)
@@ -221,7 +223,7 @@ func TestRetrieveSecretNames(t *testing.T) {
 func TestProcessNamespaceSecret(t *testing.T) {
 	clientset := createTestSecrets(t)
 
-	unusedSecrets, err := processNamespaceSecret(clientset, testNamespace, &FilterOptions{})
+	unusedSecrets, err := processNamespaceSecret(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Fatalf("Error retrieving unused secrets: %v", err)
 	}
@@ -239,11 +241,6 @@ func TestProcessNamespaceSecret(t *testing.T) {
 func TestGetUnusedSecretsStructured(t *testing.T) {
 	clientset := createTestSecrets(t)
 
-	includeExcludeLists := IncludeExcludeLists{
-		IncludeListStr: "",
-		ExcludeListStr: "",
-	}
-
 	opts := Opts{
 		WebhookURL:    "",
 		Channel:       "",
@@ -252,7 +249,7 @@ func TestGetUnusedSecretsStructured(t *testing.T) {
 		NoInteractive: true,
 	}
 
-	output, err := GetUnusedSecrets(includeExcludeLists, &FilterOptions{}, clientset, "json", opts)
+	output, err := GetUnusedSecrets(&filters.Options{}, clientset, "json", opts)
 	if err != nil {
 		t.Fatalf("Error calling GetUnusedSecretsStructured: %v", err)
 	}
diff --git a/pkg/kor/serviceaccounts_test.go b/pkg/kor/serviceaccounts_test.go
index 91c6542..f1789cb 100644
--- a/pkg/kor/serviceaccounts_test.go
+++ b/pkg/kor/serviceaccounts_test.go
@@ -12,6 +12,8 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/kubernetes/scheme"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func createTestServiceAccounts(t *testing.T) *fake.Clientset {
@@ -117,7 +119,7 @@ func TestRetrieveUsedSA(t *testing.T) {
 
 func TestRetrieveServiceAccountNames(t *testing.T) {
 	clientset := createTestServiceAccounts(t)
-	serviceAccountNames, err := retrieveServiceAccountNames(clientset, testNamespace, &FilterOptions{})
+	serviceAccountNames, err := retrieveServiceAccountNames(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Fatalf("Expected no error, got %v", err)
 	}
@@ -147,7 +149,7 @@ func TestProcessNamespaceSA(t *testing.T) {
 		t.Fatalf("Error creating fake %s: %v", "Pod", err)
 	}
 
-	unusedServiceAccounts, err := processNamespaceSA(clientset, testNamespace, &FilterOptions{})
+	unusedServiceAccounts, err := processNamespaceSA(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Fatalf("Expected no error, got %v", err)
 	}
@@ -170,11 +172,6 @@ func TestGetUnusedServiceAccountsStructured(t *testing.T) {
 		t.Fatalf("Error creating fake %s: %v", "clusterRoleBinding", err)
 	}
 
-	includeExcludeLists := IncludeExcludeLists{
-		IncludeListStr: "",
-		ExcludeListStr: "",
-	}
-
 	opts := Opts{
 		WebhookURL:    "",
 		Channel:       "",
@@ -183,7 +180,7 @@ func TestGetUnusedServiceAccountsStructured(t *testing.T) {
 		NoInteractive: true,
 	}
 
-	output, err := GetUnusedServiceAccounts(includeExcludeLists, &FilterOptions{}, clientset, "json", opts)
+	output, err := GetUnusedServiceAccounts(&filters.Options{}, clientset, "json", opts)
 	if err != nil {
 		t.Fatalf("Error calling GetUnusedServiceAccountsStructured: %v", err)
 	}
diff --git a/pkg/kor/services_test.go b/pkg/kor/services_test.go
index e7f3b75..2bec9f1 100644
--- a/pkg/kor/services_test.go
+++ b/pkg/kor/services_test.go
@@ -12,6 +12,8 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/kubernetes/scheme"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func createTestServices(t *testing.T) *fake.Clientset {
@@ -43,7 +45,7 @@ func createTestServices(t *testing.T) *fake.Clientset {
 func TestGetEndpointsWithoutSubsets(t *testing.T) {
 	clientset := createTestServices(t)
 
-	servicesWithoutEndpoints, err := ProcessNamespaceServices(clientset, testNamespace, &FilterOptions{})
+	servicesWithoutEndpoints, err := ProcessNamespaceServices(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
 	}
@@ -60,11 +62,6 @@ func TestGetEndpointsWithoutSubsets(t *testing.T) {
 func TestGetUnusedServicesStructured(t *testing.T) {
 	clientset := createTestServices(t)
 
-	includeExcludeLists := IncludeExcludeLists{
-		IncludeListStr: testNamespace,
-		ExcludeListStr: "",
-	}
-
 	opts := Opts{
 		WebhookURL:    "",
 		Channel:       "",
@@ -73,7 +70,7 @@ func TestGetUnusedServicesStructured(t *testing.T) {
 		NoInteractive: true,
 	}
 
-	output, err := GetUnusedServices(includeExcludeLists, &FilterOptions{}, clientset, "json", opts)
+	output, err := GetUnusedServices(&filters.Options{}, clientset, "json", opts)
 	if err != nil {
 		t.Fatalf("Error calling GetUnusedServicesStructured: %v", err)
 	}
diff --git a/pkg/kor/statefulsets_test.go b/pkg/kor/statefulsets_test.go
index c6c1fe2..9de6d7c 100644
--- a/pkg/kor/statefulsets_test.go
+++ b/pkg/kor/statefulsets_test.go
@@ -12,6 +12,8 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes/fake"
 	"k8s.io/client-go/kubernetes/scheme"
+
+	"github.com/yonahd/kor/pkg/filters"
 )
 
 func createTestStatefulSets(t *testing.T) *fake.Clientset {
@@ -45,7 +47,7 @@ func createTestStatefulSets(t *testing.T) *fake.Clientset {
 func TestProcessNamespaceStatefulSets(t *testing.T) {
 	clientset := createTestStatefulSets(t)
 
-	statefulSetsWithoutReplicas, err := ProcessNamespaceStatefulSets(clientset, testNamespace, &FilterOptions{})
+	statefulSetsWithoutReplicas, err := ProcessNamespaceStatefulSets(clientset, testNamespace, &filters.Options{})
 	if err != nil {
 		t.Errorf("Expected no error, got %v", err)
 	}
@@ -62,11 +64,6 @@ func TestProcessNamespaceStatefulSets(t *testing.T) {
 func TestGetUnusedStatefulSetsStructured(t *testing.T) {
 	clientset := createTestStatefulSets(t)
 
-	includeExcludeLists := IncludeExcludeLists{
-		IncludeListStr: "",
-		ExcludeListStr: "",
-	}
-
 	opts := Opts{
 		WebhookURL:    "",
 		Channel:       "",
@@ -75,7 +72,7 @@ func TestGetUnusedStatefulSetsStructured(t *testing.T) {
 		NoInteractive: true,
 	}
 
-	output, err := GetUnusedStatefulSets(includeExcludeLists, &FilterOptions{}, clientset, "json", opts)
+	output, err := GetUnusedStatefulSets(&filters.Options{}, clientset, "json", opts)
 	if err != nil {
 		t.Fatalf("Error calling GetUnusedStatefulSetsStructured: %v", err)
 	}