Repository: yonahd/kor
Pull request #216 (2024-03-01 01:05:33)

feat: unused StorageClasses
This PR adds a new functionality to `kor` by finding unused StorageClasses.
I've considered StorageClasses to be unused, if they aren't used by any PVs/PVCs.

```
$ kor storageclass
kor version: vdev

  _  _____  ____
 | |/ / _ \|  _ \
 | ' / | | | |_) |
 | . \ |_| |  _ <
 |_|\_\___/|_| \_\

Unused StorageClasses in Namespace:
+---+---------------------+
| # |    RESOURCE NAME    |
+---+---------------------+
| 1 | example-vol-default |
+---+---------------------+

```

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/yonahd/kor/pull/216?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Yonah+Dissen) Report
Attention: Patch coverage is `52.94118%` with `56 lines` in your changes are missing coverage. Please review.
> Project coverage is 47.90%. Comparing base [(`379f27a`)](https://app.codecov.io/gh/yonahd/kor/commit/379f27af36911db925e067581c11a5a86f1165ab?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Yonah+Dissen) to head [(`f7080ff`)](https://app.codecov.io/gh/yonahd/kor/pull/216?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Yonah+Dissen).

| [Files](https://app.codecov.io/gh/yonahd/kor/pull/216?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Yonah+Dissen) | Patch % | Lines |
|---|---|---|
| [pkg/kor/storageclasses.go](https://app.codecov.io/gh/yonahd/kor/pull/216?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Yonah+Dissen#diff-cGtnL2tvci9zdG9yYWdlY2xhc3Nlcy5nbw==) | 60.00% | [22 Missing and 10 partials :warning: ](https://app.codecov.io/gh/yonahd/kor/pull/216?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Yonah+Dissen) |
| [pkg/kor/all.go](https://app.codecov.io/gh/yonahd/kor/pull/216?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Yonah+Dissen#diff-cGtnL2tvci9hbGwuZ28=) | 0.00% | [13 Missing :warning: ](https://app.codecov.io/gh/yonahd/kor/pull/216?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Yonah+Dissen) |
| [pkg/kor/delete.go](https://app.codecov.io/gh/yonahd/kor/pull/216?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Yonah+Dissen#diff-cGtnL2tvci9kZWxldGUuZ28=) | 0.00% | [7 Missing :warning: ](https://app.codecov.io/gh/yonahd/kor/pull/216?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Yonah+Dissen) |
| [pkg/kor/multi.go](https://app.codecov.io/gh/yonahd/kor/pull/216?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Yonah+Dissen#diff-cGtnL2tvci9tdWx0aS5nbw==) | 0.00% | [4 Missing :warning: ](https://app.codecov.io/gh/yonahd/kor/pull/216?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Yonah+Dissen) |

:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #216      +/-   ##
==========================================
+ Coverage   47.71%   47.90%   +0.18%     
==========================================
  Files          31       32       +1     
  Lines        2741     2858     +117     
==========================================
+ Hits         1308     1369      +61     
- Misses       1274     1320      +46     
- Partials      159      169      +10     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/yonahd/kor/pull/216?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Yonah+Dissen).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Yonah+Dissen).


Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 84403ea..90091ea 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -33,13 +33,13 @@ jobs:
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
-          
+
       - name: Extract metadata for the Docker image
         id: meta
         uses: docker/metadata-action@v5
         with:
           images: ${{ secrets.DOCKERHUB_USERNAME }}/kor
-  
+
       - name: Build and push the Docker image
         uses: docker/build-push-action@v5
         with:
@@ -47,4 +47,6 @@ jobs:
           push: true
           tags: ${{ steps.meta.outputs.tags }}
           labels: ${{ steps.meta.outputs.labels }}
-          
+
+      - name: Update new version in krew-index
+        uses: rajatjindal/krew-release-bot@v0.0.46
diff --git a/.krew.yaml b/.krew.yaml
new file mode 100644
index 0000000..ae01867
--- /dev/null
+++ b/.krew.yaml
@@ -0,0 +1,43 @@
+apiVersion: krew.googlecontainertools.github.com/v1alpha2
+kind: Plugin
+metadata:
+  name: kor
+spec:
+  version: {{ .TagName }}
+  homepage: https://github.com/yonahd/kor
+  shortDescription: Kor is a tool to discover unused K8s resources.
+  description: |
+    This plugin shows unused Kubernetes resources (namespaced or cluster-scoped).
+    It provides additional capabilities such as exporting Prometheus metrics,
+    deleting unused resources, and more!
+  platforms:
+  - selector:
+      matchLabels:
+        os: linux
+        arch: amd64
+    {{ addURIAndSha "https://github.com/yonahd/kor/releases/download/{{ .TagName }}/kor_Linux_x86_64.tar.gz" .TagName }}
+    bin: kor
+  - selector:
+      matchLabels:
+        os: linux
+        arch: arm64
+    {{ addURIAndSha "https://github.com/yonahd/kor/releases/download/{{ .TagName }}/kor_Linux_arm64.tar.gz" .TagName }}
+    bin: kor
+  - selector:
+      matchLabels:
+        os: darwin
+        arch: amd64
+    {{ addURIAndSha "https://github.com/yonahd/kor/releases/download/{{ .TagName }}/kor_Darwin_x86_64.tar.gz" .TagName }}
+    bin: kor
+  - selector:
+      matchLabels:
+        os: darwin
+        arch: arm64
+    {{ addURIAndSha "https://github.com/yonahd/kor/releases/download/{{ .TagName }}/kor_Darwin_arm64.tar.gz" .TagName }}
+    bin: kor
+  - selector:
+      matchLabels:
+        os: windows
+        arch: amd64
+    {{ addURIAndSha "https://github.com/yonahd/kor/releases/download/{{ .TagName }}/kor_Windows_x86_64.zip" .TagName }}
+    bin: kor.exe
diff --git a/README.md b/README.md
index fe6b1e0..08736f4 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,7 @@ Kor is a tool to discover unused Kubernetes resources. Currently, Kor can identi
 - Jobs
 - ReplicaSets
 - DaemonSets
+- StorageClasses
 
 ![Kor Screenshot](/images/screenshot.png)
 
@@ -55,6 +56,11 @@ docker run --rm -i yonahdissen/kor
 docker run --rm -i -v "/path/to/.kube/config:/root/.kube/config" yonahdissen/kor all
 ```
 
+### Kubectl plugin (<img src="https://raw.githubusercontent.com/kubernetes-sigs/krew/master/assets/logo/horizontal/color/krew-horizontal-color.png" alt="krew" width="48"/>)
+```sh
+kubectl krew install kor
+```
+
 ### Helm
 Run as a cronjob in your Cluster (with an option for sending slack updates)
 ```sh
@@ -92,10 +98,11 @@ Kor provides various subcommands to identify and list unused resources. The avai
 - `hpa` - Gets unused HPAs for the specified namespace or all namespaces.
 - `pods` - Gets unused Pods for the specified namespace or all namespaces.
 - `pvc` - Gets unused PVCs for the specified namespace or all namespaces.
-- `pv` - Gets unused PVs in the cluster(non namespaced resource).
+- `pv` - Gets unused PVs in the cluster (non namespaced resource).
+- `storageclasses` - Gets unused StorageClasses in the cluster (non namespaced resource).
 - `ingress` - Gets unused Ingresses for the specified namespace or all namespaces.
 - `pdb` - Gets unused PDBs for the specified namespace or all namespaces.
-- `crd` - Gets unused CRDs in the cluster(non namespaced resource).
+- `crd` - Gets unused CRDs in the cluster (non namespaced resource).
 - `jobs` - Gets unused jobs for the specified namespace or all namespaces.
 - `replicasets` - Gets unused replicaSets for the specified namespace or all namespaces.
 - `daemonsets`- Gets unused DaemonSets for the specified namespace or all namespaces.
@@ -155,6 +162,7 @@ kor [subcommand] --help
 | Jobs            | Jobs status is completed                                                                                                                                                                                                          |                                                                                                                              |
 | ReplicaSets     | replicaSets that specify replicas to 0 and has already completed it's work                                                                                                                                                        |
 | DaemonSets     | DaemonSets not scheduled on any nodes              |
+| StorageClasses | StorageClasses not used by any PVs/PVCs |
 
 ## Deleting Unused resources
 If you want to delete resources in an interactive way using Kor you can run:
diff --git a/charts/kor/templates/role.yaml b/charts/kor/templates/role.yaml
index 5ab88c2..e533cc9 100644
--- a/charts/kor/templates/role.yaml
+++ b/charts/kor/templates/role.yaml
@@ -55,13 +55,14 @@ rules:
       - endpoints
       - jobs
       - replicasets
-      - daemonsets      
+      - daemonsets
       {{/* cluster-scoped resources */}}
       - namespaces
       - clusterroles
       - clusterrolebindings
       - persistentvolumes
       - customresourcedefinitions
+      - storageclasses
     verbs:
       - get
       - list
diff --git a/cmd/kor/root.go b/cmd/kor/root.go
index ee703be..f82f4f5 100644
--- a/cmd/kor/root.go
+++ b/cmd/kor/root.go
@@ -3,6 +3,8 @@ package kor
 import (
 	"fmt"
 	"os"
+	"path/filepath"
+	"strings"
 
 	"github.com/spf13/cobra"
 
@@ -11,8 +13,17 @@ import (
 	"github.com/yonahd/kor/pkg/utils"
 )
 
+func execName() string {
+	n := "kor"
+	if strings.HasPrefix(filepath.Base(os.Args[0]), "kubectl-") {
+		return "kubectl-" + n
+	}
+
+	return n
+}
+
 var rootCmd = &cobra.Command{
-	Use:   "kor",
+	Use:   execName(),
 	Short: "kor - a CLI to to discover unused Kubernetes resources",
 	Long: `kor is a CLI to to discover unused Kubernetes resources
 	kor can currently discover unused configmaps and secrets`,
diff --git a/cmd/kor/storageclasses.go b/cmd/kor/storageclasses.go
new file mode 100644
index 0000000..eade9e0
--- /dev/null
+++ b/cmd/kor/storageclasses.go
@@ -0,0 +1,32 @@
+package kor
+
+import (
+	"fmt"
+
+	"github.com/spf13/cobra"
+
+	"github.com/yonahd/kor/pkg/kor"
+	"github.com/yonahd/kor/pkg/utils"
+)
+
+var scCmd = &cobra.Command{
+	Use:     "storageclass",
+	Aliases: []string{"sc", "storageclassses"},
+	Short:   "Gets unused storageClasses",
+	Args:    cobra.NoArgs,
+	Run: func(cmd *cobra.Command, args []string) {
+		clientset := kor.GetKubeClient(kubeconfig)
+
+		if response, err := kor.GetUnusedStorageClasses(filterOptions, clientset, outputFormat, opts); err != nil {
+			fmt.Println(err)
+		} else {
+			utils.PrintLogo(outputFormat)
+			fmt.Println(response)
+		}
+
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(scCmd)
+}
diff --git a/go.mod b/go.mod
index a2ac152..8d3fa94 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.21
 require (
 	github.com/fatih/color v1.16.0
 	github.com/olekukonko/tablewriter v0.0.5
-	github.com/prometheus/client_golang v1.18.0
+	github.com/prometheus/client_golang v1.19.0
 	github.com/spf13/cobra v1.8.0
 	k8s.io/api v0.29.2
 	k8s.io/apiextensions-apiserver v0.29.2
@@ -38,24 +38,23 @@ require (
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/mattn/go-runewidth v0.0.14 // indirect
-	github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/prometheus/client_model v0.5.0 // indirect
-	github.com/prometheus/common v0.45.0 // indirect
+	github.com/prometheus/common v0.48.0 // indirect
 	github.com/prometheus/procfs v0.12.0 // indirect
 	github.com/rivo/uniseg v0.4.4 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
-	golang.org/x/net v0.19.0 // indirect
-	golang.org/x/oauth2 v0.12.0 // indirect
-	golang.org/x/sys v0.15.0 // indirect
-	golang.org/x/term v0.15.0 // indirect
+	golang.org/x/net v0.20.0 // indirect
+	golang.org/x/oauth2 v0.16.0 // indirect
+	golang.org/x/sys v0.16.0 // indirect
+	golang.org/x/term v0.16.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	golang.org/x/time v0.3.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/protobuf v1.31.0 // indirect
+	google.golang.org/protobuf v1.32.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/go.sum b/go.sum
index b6c31b1..bbb62f3 100644
--- a/go.sum
+++ b/go.sum
@@ -71,8 +71,6 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
 github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
-github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -90,12 +88,12 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
-github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
+github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
+github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
 github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
 github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
-github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
-github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
+github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
+github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
 github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
 github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -129,10 +127,10 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
-golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
-golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
-golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
+golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
+golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
+golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
+golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -141,10 +139,10 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
-golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
+golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
+golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -166,8 +164,8 @@ google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
-google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
+google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/pkg/kor/all.go b/pkg/kor/all.go
index 6b42df3..4fe8720 100644
--- a/pkg/kor/all.go
+++ b/pkg/kor/all.go
@@ -185,7 +185,16 @@ func getUnusedDaemonSets(clientset kubernetes.Interface, namespace string, filte
 	return namespaceSADiff
 }
 
-func GetUnusedAll(filterOpts *filters.Options, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) (string, error) {
+func getUnusedStorageClasses(clientset kubernetes.Interface, filterOpts *filters.Options) ResourceDiff {
+	scDiff, err := processStorageClasses(clientset, filterOpts)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Failed to get %s: %v\n", "StorageClasses", err)
+	}
+	allScDiff := ResourceDiff{"StorageClass", scDiff}
+	return allScDiff
+}
+
+func GetUnusedAllNamespaced(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
 	var outputBuffer bytes.Buffer
 
 	namespaces := filterOpts.Namespaces(clientset)
@@ -234,6 +243,23 @@ func GetUnusedAll(filterOpts *filters.Options, clientset kubernetes.Interface, a
 		response[namespace] = resourceMap
 	}
 
+	jsonResponse, err := json.MarshalIndent(response, "", "  ")
+	if err != nil {
+		return "", err
+	}
+
+	unusedAllNamespaced, err := unusedResourceFormatter(outputFormat, outputBuffer, opts, jsonResponse)
+	if err != nil {
+		fmt.Printf("err: %v\n", err)
+	}
+
+	return unusedAllNamespaced, nil
+}
+
+func GetUnusedAllNonNamespaced(filterOpts *filters.Options, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) (string, error) {
+	var outputBuffer bytes.Buffer
+	response := make(map[string]map[string][]string)
+
 	var allDiffs []ResourceDiff
 	noNamespaceResourceMap := make(map[string][]string)
 	crdDiff := getUnusedCrds(apiExtClient, dynamicClient, filterOpts)
@@ -254,6 +280,12 @@ func GetUnusedAll(filterOpts *filters.Options, clientset kubernetes.Interface, a
 	outputBuffer.WriteString("\n")
 	noNamespaceResourceMap[clusterRoleDiff.resourceType] = clusterRoleDiff.diff
 
+	storageClassDiff := getUnusedStorageClasses(clientset, filterOpts)
+	storageClassOutput := FormatOutputAll("", []ResourceDiff{storageClassDiff}, opts)
+	outputBuffer.WriteString(storageClassOutput)
+	outputBuffer.WriteString("\n")
+	noNamespaceResourceMap[storageClassDiff.resourceType] = storageClassDiff.diff
+
 	output := FormatOutputAll("", allDiffs, opts)
 
 	outputBuffer.WriteString(output)
@@ -265,10 +297,26 @@ func GetUnusedAll(filterOpts *filters.Options, clientset kubernetes.Interface, a
 		return "", err
 	}
 
-	unusedAll, err := unusedResourceFormatter(outputFormat, outputBuffer, opts, jsonResponse)
+	unusedAllNonNamespaced, err := unusedResourceFormatter(outputFormat, outputBuffer, opts, jsonResponse)
+	if err != nil {
+		fmt.Printf("err: %v\n", err)
+	}
+
+	return unusedAllNonNamespaced, nil
+}
+
+func GetUnusedAll(filterOpts *filters.Options, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) (string, error) {
+	unusedAllNamespaced, err := GetUnusedAllNamespaced(filterOpts, clientset, outputFormat, opts)
+	if err != nil {
+		fmt.Printf("err: %v\n", err)
+	}
+
+	unusedAllNonNamespaced, err := GetUnusedAllNonNamespaced(filterOpts, clientset, apiExtClient, dynamicClient, outputFormat, opts)
 	if err != nil {
 		fmt.Printf("err: %v\n", err)
 	}
 
+	unusedAll := unusedAllNamespaced + unusedAllNonNamespaced
+
 	return unusedAll, nil
 }
diff --git a/pkg/kor/delete.go b/pkg/kor/delete.go
index 1ef0e6c..5d78a16 100644
--- a/pkg/kor/delete.go
+++ b/pkg/kor/delete.go
@@ -15,6 +15,7 @@ import (
 	networkingv1 "k8s.io/api/networking/v1"
 	policyv1beta1 "k8s.io/api/policy/v1beta1"
 	rbacv1 "k8s.io/api/rbac/v1"
+	storagev1 "k8s.io/api/storage/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
@@ -75,6 +76,9 @@ func DeleteResourceCmd() map[string]func(clientset kubernetes.Interface, namespa
 		"DaemonSet": func(clientset kubernetes.Interface, namespace, name string) error {
 			return clientset.AppsV1().DaemonSets(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
 		},
+		"StorageClass": func(clientset kubernetes.Interface, namespace, name string) error {
+			return clientset.StorageV1().StorageClasses().Delete(context.TODO(), name, metav1.DeleteOptions{})
+		},
 	}
 
 	return deleteResourceApiMap
@@ -160,6 +164,8 @@ func updateResource(clientset kubernetes.Interface, namespace, resourceType stri
 		return clientset.AppsV1().ReplicaSets(namespace).Update(context.TODO(), resource.(*appsv1.ReplicaSet), metav1.UpdateOptions{})
 	case "DaemonSet":
 		return clientset.AppsV1().DaemonSets(namespace).Update(context.TODO(), resource.(*appsv1.DaemonSet), metav1.UpdateOptions{})
+	case "StorageClass":
+		return clientset.StorageV1().StorageClasses().Update(context.TODO(), resource.(*storagev1.StorageClass), metav1.UpdateOptions{})
 	}
 	return nil, fmt.Errorf("resource type '%s' is not supported", resourceType)
 }
@@ -200,6 +206,8 @@ func getResource(clientset kubernetes.Interface, namespace, resourceType, resour
 		return clientset.AppsV1().ReplicaSets(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{})
 	case "DaemonSet":
 		return clientset.AppsV1().DaemonSets(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{})
+	case "StorageClass":
+		return clientset.StorageV1().StorageClasses().Get(context.TODO(), resourceName, metav1.GetOptions{})
 	}
 	return nil, fmt.Errorf("resource type '%s' is not supported", resourceType)
 }
diff --git a/pkg/kor/multi.go b/pkg/kor/multi.go
index 1cdf645..17ced67 100644
--- a/pkg/kor/multi.go
+++ b/pkg/kor/multi.go
@@ -33,6 +33,10 @@ func retrieveNoNamespaceDiff(clientset kubernetes.Interface, apiExtClient apiext
 			clusterRoleDiff := getUnusedClusterRoles(clientset, filterOpts)
 			noNamespaceDiff = append(noNamespaceDiff, clusterRoleDiff)
 			markedForRemoval[counter] = true
+		case "sc", "storageclass", "storageclasses":
+			storageClassDiff := getUnusedStorageClasses(clientset, filterOpts)
+			noNamespaceDiff = append(noNamespaceDiff, storageClassDiff)
+			markedForRemoval[counter] = true
 		}
 	}
 
diff --git a/pkg/kor/storageclasses.go b/pkg/kor/storageclasses.go
new file mode 100644
index 0000000..82c0dee
--- /dev/null
+++ b/pkg/kor/storageclasses.go
@@ -0,0 +1,124 @@
+package kor
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"fmt"
+	"os"
+
+	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 retrieveUsedStorageClasses(clientset kubernetes.Interface) ([]string, error) {
+	pvs, err := clientset.CoreV1().PersistentVolumes().List(context.TODO(), metav1.ListOptions{})
+	if err != nil {
+		fmt.Printf("Failed to list PVs: %v\n", err)
+		os.Exit(1)
+	}
+
+	pvcs, err := clientset.CoreV1().PersistentVolumeClaims("").List(context.TODO(), metav1.ListOptions{})
+	if err != nil {
+		fmt.Printf("Failed to list PVCs: %v\n", err)
+		os.Exit(1)
+	}
+
+	var usedStorageClasses []string
+
+	// Iterate through each PV and check for StorageClass usage
+	for _, pv := range pvs.Items {
+		if pv.Spec.StorageClassName != "" {
+			usedStorageClasses = append(usedStorageClasses, pv.Spec.StorageClassName)
+		}
+	}
+
+	// Iterate through each PVC and check for StorageClass usage
+	for _, pvc := range pvcs.Items {
+		if pvc.Spec.StorageClassName != nil {
+			usedStorageClasses = append(usedStorageClasses, *pvc.Spec.StorageClassName)
+		}
+	}
+
+	return usedStorageClasses, err
+}
+
+func processStorageClasses(clientset kubernetes.Interface, filterOpts *filters.Options) ([]string, error) {
+	scs, err := clientset.StorageV1().StorageClasses().List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
+	if err != nil {
+		return nil, err
+	}
+
+	var unusedStorageClassNames []string
+	storageClassNames := make([]string, 0, len(scs.Items))
+
+	for _, sc := range scs.Items {
+		if pass := filters.KorLabelFilter(&sc, &filters.Options{}); pass {
+			continue
+		}
+
+		if sc.Labels["kor/used"] == "false" {
+			unusedStorageClassNames = append(unusedStorageClassNames, sc.Name)
+			continue
+		}
+
+		storageClassNames = append(storageClassNames, sc.Name)
+	}
+
+	usedStorageClasses, err := retrieveUsedStorageClasses(clientset)
+	if err != nil {
+		return nil, err
+	}
+
+	diff := CalculateResourceDifference(usedStorageClasses, storageClassNames)
+	diff = append(diff, unusedStorageClassNames...)
+	return diff, nil
+}
+
+func GetUnusedStorageClasses(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) {
+	var outputBuffer bytes.Buffer
+	response := make(map[string]map[string][]string)
+
+	diff, err := processStorageClasses(clientset, filterOpts)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Failed to process storageClasses: %v\n", err)
+	}
+
+	if len(diff) > 0 {
+		// We consider cluster scope resources in "" (empty string) namespace, as it is common in k8s
+		if response[""] == nil {
+			response[""] = make(map[string][]string)
+		}
+		response[""]["StorageClass"] = diff
+	}
+
+	if opts.DeleteFlag {
+		if diff, err = DeleteResource(diff, clientset, "", "StorageClass", opts.NoInteractive); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to delete StorageClass %s: %v\n", diff, err)
+		}
+	}
+
+	output := FormatOutput("", diff, "StorageClasses", opts)
+	if output != "" {
+		outputBuffer.WriteString(output)
+		outputBuffer.WriteString("\n")
+
+		response[""]["StorageClass"] = diff
+
+	}
+
+	jsonResponse, err := json.MarshalIndent(response, "", "  ")
+	if err != nil {
+		return "", err
+	}
+
+	unusedStorageClasses, err := unusedResourceFormatter(outputFormat, outputBuffer, opts, jsonResponse)
+	if err != nil {
+		fmt.Printf("err: %v\n", err)
+	}
+
+	return unusedStorageClasses, nil
+}


Test patch:
diff --git a/pkg/kor/create_test_resources.go b/pkg/kor/create_test_resources.go
index d07c9d8..449cef6 100644
--- a/pkg/kor/create_test_resources.go
+++ b/pkg/kor/create_test_resources.go
@@ -8,7 +8,8 @@ import (
 	networkingv1 "k8s.io/api/networking/v1"
 	policyv1 "k8s.io/api/policy/v1"
 	rbacv1 "k8s.io/api/rbac/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1"
+	storagev1 "k8s.io/api/storage/v1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 )
 
@@ -219,28 +220,43 @@ func CreateTestIngress(namespace, name, ServiceName, secretName string, labels m
 	}
 }
 
-func CreateTestPvc(namespace, name string, labels map[string]string) *corev1.PersistentVolumeClaim {
+func CreateTestPvc(namespace, name string, labels map[string]string, storageClass string) *corev1.PersistentVolumeClaim {
 	return &corev1.PersistentVolumeClaim{
 		ObjectMeta: v1.ObjectMeta{
 			Namespace: namespace,
 			Name:      name,
 			Labels:    labels,
 		},
+		Spec: corev1.PersistentVolumeClaimSpec{
+			StorageClassName: &storageClass,
+		},
 	}
 }
 
-func CreateTestPv(name, phase string, labels map[string]string) *corev1.PersistentVolume {
+func CreateTestPv(name, phase string, labels map[string]string, storageClass string) *corev1.PersistentVolume {
 	return &corev1.PersistentVolume{
 		ObjectMeta: v1.ObjectMeta{
 			Name:   name,
 			Labels: labels,
 		},
+		Spec: corev1.PersistentVolumeSpec{
+			StorageClassName: storageClass,
+		},
 		Status: corev1.PersistentVolumeStatus{
 			Phase: corev1.PersistentVolumePhase(phase),
 		},
 	}
 }
 
+func CreateTestStorageClass(name, provisioner string) *storagev1.StorageClass {
+	return &storagev1.StorageClass{
+		ObjectMeta: v1.ObjectMeta{
+			Name: name,
+		},
+		Provisioner: provisioner,
+	}
+}
+
 func CreateTestPdb(namespace, name string, matchLabels, pdbLabels map[string]string) *policyv1.PodDisruptionBudget {
 	return &policyv1.PodDisruptionBudget{
 		ObjectMeta: v1.ObjectMeta{
diff --git a/pkg/kor/pv_test.go b/pkg/kor/pv_test.go
index 70b0b6b..70e16ad 100644
--- a/pkg/kor/pv_test.go
+++ b/pkg/kor/pv_test.go
@@ -15,25 +15,25 @@ import (
 func createTestPvs(t *testing.T) *fake.Clientset {
 	clientset := fake.NewSimpleClientset()
 
-	pv1 := CreateTestPv("test-pv1", "Bound", AppLabels)
+	pv1 := CreateTestPv("test-pv1", "Bound", AppLabels, "test-sc1")
 	_, err := clientset.CoreV1().PersistentVolumes().Create(context.TODO(), pv1, v1.CreateOptions{})
 	if err != nil {
 		t.Fatalf("Error creating fake %s: %v", "PV", err)
 	}
 
-	pv2 := CreateTestPv("test-pv2", "Available", AppLabels)
+	pv2 := CreateTestPv("test-pv2", "Available", AppLabels, "test-sc1")
 	_, err = clientset.CoreV1().PersistentVolumes().Create(context.TODO(), pv2, v1.CreateOptions{})
 	if err != nil {
 		t.Fatalf("Error creating fake %s: %v", "PV", err)
 	}
 
-	pv3 := CreateTestPv("test-pv3", "Bound", UsedLabels)
+	pv3 := CreateTestPv("test-pv3", "Bound", UsedLabels, "test-sc1")
 	_, err = clientset.CoreV1().PersistentVolumes().Create(context.TODO(), pv3, v1.CreateOptions{})
 	if err != nil {
 		t.Fatalf("Error creating fake %s: %v", "PV", err)
 	}
 
-	pv4 := CreateTestPv("test-pv4", "Available", UnusedLabels)
+	pv4 := CreateTestPv("test-pv4", "Available", UnusedLabels, "test-sc1")
 	_, err = clientset.CoreV1().PersistentVolumes().Create(context.TODO(), pv4, v1.CreateOptions{})
 	if err != nil {
 		t.Fatalf("Error creating fake %s: %v", "PV", err)
diff --git a/pkg/kor/pvc_test.go b/pkg/kor/pvc_test.go
index 0bc5f75..5f96b8e 100644
--- a/pkg/kor/pvc_test.go
+++ b/pkg/kor/pvc_test.go
@@ -28,25 +28,25 @@ func createTestPvcs(t *testing.T) *fake.Clientset {
 		t.Fatalf("Error creating namespace %s: %v", testNamespace, err)
 	}
 
-	pvc1 := CreateTestPvc(testNamespace, "test-pvc1", AppLabels)
+	pvc1 := CreateTestPvc(testNamespace, "test-pvc1", AppLabels, "test-sc1")
 	_, err = clientset.CoreV1().PersistentVolumeClaims(testNamespace).Create(context.TODO(), pvc1, v1.CreateOptions{})
 	if err != nil {
 		t.Fatalf("Error creating fake %s: %v", "Pvc", err)
 	}
 
-	pvc2 := CreateTestPvc(testNamespace, "test-pvc2", AppLabels)
+	pvc2 := CreateTestPvc(testNamespace, "test-pvc2", AppLabels, "test-sc1")
 	_, err = clientset.CoreV1().PersistentVolumeClaims(testNamespace).Create(context.TODO(), pvc2, v1.CreateOptions{})
 	if err != nil {
 		t.Fatalf("Error creating fake %s: %v", "Pvc", err)
 	}
 
-	pvc3 := CreateTestPvc(testNamespace, "test-pvc3", UsedLabels)
+	pvc3 := CreateTestPvc(testNamespace, "test-pvc3", UsedLabels, "test-sc1")
 	_, err = clientset.CoreV1().PersistentVolumeClaims(testNamespace).Create(context.TODO(), pvc3, v1.CreateOptions{})
 	if err != nil {
 		t.Fatalf("Error creating fake %s: %v", "Pvc", err)
 	}
 
-	pvc4 := CreateTestPvc(testNamespace, "test-pvc4", UnusedLabels)
+	pvc4 := CreateTestPvc(testNamespace, "test-pvc4", UnusedLabels, "test-sc1")
 	_, err = clientset.CoreV1().PersistentVolumeClaims(testNamespace).Create(context.TODO(), pvc4, v1.CreateOptions{})
 	if err != nil {
 		t.Fatalf("Error creating fake %s: %v", "Pvc", err)
diff --git a/pkg/kor/storageclasses_test.go b/pkg/kor/storageclasses_test.go
new file mode 100644
index 0000000..0c198ea
--- /dev/null
+++ b/pkg/kor/storageclasses_test.go
@@ -0,0 +1,99 @@
+package kor
+
+import (
+	"context"
+	"encoding/json"
+	"reflect"
+	"testing"
+
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes/fake"
+
+	"github.com/yonahd/kor/pkg/filters"
+)
+
+func createTestStorageClass(t *testing.T) *fake.Clientset {
+	clientset := fake.NewSimpleClientset()
+
+	sc1 := CreateTestStorageClass("test-sc1", "kor.com")
+	_, err := clientset.StorageV1().StorageClasses().Create(context.TODO(), sc1, v1.CreateOptions{})
+	if err != nil {
+		t.Fatalf("Error creating fake %s: %v", "StorageClass", err)
+	}
+
+	return clientset
+}
+
+func TestRetrieveUsedStorageClassesFromPVCs(t *testing.T) {
+	clientset := createTestPvcs(t)
+	usedStorageClasses, err := retrieveUsedStorageClasses(clientset)
+
+	if err != nil {
+		t.Errorf("Expected no error, got %v", err)
+	}
+
+	if !contains(usedStorageClasses, "test-sc1") {
+		t.Errorf("Expected 'test-sc1', got %v", usedStorageClasses)
+	}
+}
+
+func TestRetrieveUsedStorageClassesFromPVs(t *testing.T) {
+	clientset := createTestPvs(t)
+	usedStorageClasses, err := retrieveUsedStorageClasses(clientset)
+
+	if err != nil {
+		t.Errorf("Expected no error, got %v", err)
+	}
+
+	if !contains(usedStorageClasses, "test-sc1") {
+		t.Errorf("Expected 'test-sc1', got %v", usedStorageClasses)
+	}
+}
+
+func TestProcessStorageClasses(t *testing.T) {
+	clientset := createTestStorageClass(t)
+	unusedStorageClasses, err := processStorageClasses(clientset, &filters.Options{})
+	if err != nil {
+		t.Errorf("Expected no error, got %v", err)
+	}
+
+	if len(unusedStorageClasses) != 1 {
+		t.Errorf("Expected 1 used StorageClasses, got %d", len(unusedStorageClasses))
+	}
+
+	if unusedStorageClasses[0] != "test-sc1" {
+		t.Errorf("Expected 'test-sc1', got %s", unusedStorageClasses[0])
+	}
+}
+
+func TestGetUnusedStorageClassesStructured(t *testing.T) {
+	clientset := createTestStorageClass(t)
+
+	opts := Opts{
+		WebhookURL:    "",
+		Channel:       "",
+		Token:         "",
+		DeleteFlag:    false,
+		NoInteractive: true,
+	}
+
+	output, err := GetUnusedStorageClasses(&filters.Options{}, clientset, "json", opts)
+	if err != nil {
+		t.Fatalf("Error calling GetUnusedStorageClasses: %v", err)
+	}
+
+	expectedOutput := map[string]map[string][]string{
+		"": {
+			"StorageClass": {"test-sc1"},
+		},
+	}
+
+	var actualOutput map[string]map[string][]string
+	if err := json.Unmarshal([]byte(output), &actualOutput); err != nil {
+		t.Fatalf("Error unmarshaling actual output: %v", err)
+	}
+
+	if !reflect.DeepEqual(expectedOutput, actualOutput) {
+		t.Errorf("Expected output does not match actual output")
+	}
+}