diff --git a/README.md b/README.md
index 3f117ef4..a9955a39 100644
--- a/README.md
+++ b/README.md
@@ -179,8 +179,8 @@ kor [subcommand] --help
| Resource | What it looks for | Known False Positives ⚠️ |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| ConfigMaps | ConfigMaps not used in the following places:
- Pods
- Containers
- ConfigMaps used through Volumes
- ConfigMaps used through environment variables | ConfigMaps used by resources which don't explicitly state them in the config.
e.g Grafana dashboards loaded dynamically OPA policies fluentd configs CRD configs |
-| Secrets | Secrets not used in the following places:
- Pods
- Containers
- Secrets used through volumes
- Secrets used through environment variables
- Secrets used by Ingress TLS
- Secrets used by ServiceAccounts | Secrets used by resources which don't explicitly state them in the config e.g. secrets used by CRDs |
+| ConfigMaps | ConfigMaps not used in the following places:
- Pods
- Containers
- ConfigMaps used through Volumes
- ConfigMaps used through environment variables
- **Argo WorkflowTemplates** (when CRD is present) | ConfigMaps used by resources which don't explicitly state them in the config.
e.g Grafana dashboards loaded dynamically OPA policies fluentd configs CRD configs (excluding Argo WorkflowTemplates) |
+| Secrets | Secrets not used in the following places:
- Pods
- Containers
- Secrets used through volumes
- Secrets used through environment variables
- Secrets used by Ingress TLS
- Secrets used by ServiceAccounts
- **Argo WorkflowTemplates** (when CRD is present) | Secrets used by resources which don't explicitly state them in the config e.g. secrets used by CRDs (excluding Argo WorkflowTemplates) |
| Services | Services with no endpoints | |
| Deployments | Deployments with no replicas | |
| ServiceAccounts | ServiceAccounts unused by Pods
ServiceAccounts unused by RoleBinding or ClusterRoleBinding | |
@@ -189,7 +189,7 @@ kor [subcommand] --help
| ClusterRoles | ClusterRoles not used in RoleBinding or ClusterRoleBinding
ClusterRoles not used in ClusterRole aggregation | |
| RoleBindings | RoleBindings referencing invalid Role, ClusterRole, or ServiceAccounts | |
| ClusterRoleBindings | ClusterRoleBindings referencing invalid ClusterRole or ServiceAccounts | |
-| PVCs | PVCs not used in Pods | |
+| PVCs | PVCs not used in the following places:
- Pods
- **Argo WorkflowTemplates** (when CRD is present) | |
| Ingresses | Ingresses not pointing at any Service | |
| HPAs | HPAs not used in Deployments
HPAs not used in StatefulSets | |
| CRDs | CRDs not used the cluster | |
@@ -202,6 +202,50 @@ kor [subcommand] --help
| NetworkPolicies | NetworkPolicies with no Pods selected by podSelector or Ingress / Egress rules |
| VolumeAttachments | VolumeAttachments referencing a non-existent Node, PV, or CSIDriver |
+## Argo Workflows Integration
+
+Kor automatically detects and scans **Argo Workflows WorkflowTemplate** CRDs to prevent false positives for ConfigMaps, Secrets, and PVCs referenced by WorkflowTemplates.
+
+### Key Features
+
+- **Automatic**: Detects Argo Workflows installation, works with all existing commands
+- **Zero Configuration**: No setup required, activates only when WorkflowTemplate CRD exists
+- **Comprehensive**: Scans environment variables, envFrom, volumes, projected volumes, and synchronization semaphores
+
+### Supported Reference Patterns
+
+Kor scans WorkflowTemplates for the following resource references:
+
+**ConfigMaps:**
+- Environment variables: `env[].valueFrom.configMapKeyRef`
+- Environment from: `envFrom[].configMapRef`
+- Volumes: `volumes[].configMap`
+- Projected volumes: `volumes[].projected.sources[].configMap`
+- Synchronization semaphores: `spec.synchronization.semaphore.configMapKeyRef`
+
+**Secrets:**
+- Environment variables: `env[].valueFrom.secretKeyRef`
+- Environment from: `envFrom[].secretRef`
+- Volumes: `volumes[].secret`
+- Projected volumes: `volumes[].projected.sources[].secret`
+
+**PVCs:**
+- Volumes: `volumes[].persistentVolumeClaim`
+
+### Before vs After
+
+**Before (False Positives):**
+```bash
+$ kor configmap --include-namespaces production
+Unused ConfigMaps: workflow-config, app-settings
+```
+
+**After (Accurate):**
+```bash
+$ kor configmap --include-namespaces production
+No unused ConfigMaps found
+```
+
### Deleting Unused resources
If you want to delete resources in an interactive way using Kor you can run:
diff --git a/cmd/kor/configmaps.go b/cmd/kor/configmaps.go
index 161b2a61..7d713978 100644
--- a/cmd/kor/configmaps.go
+++ b/cmd/kor/configmaps.go
@@ -16,7 +16,8 @@ var configmapCmd = &cobra.Command{
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
clientset := kor.GetKubeClient(kubeconfig)
- if response, err := kor.GetUnusedConfigmaps(filterOptions, clientset, outputFormat, opts); err != nil {
+ dynamicClient := kor.GetDynamicClient(kubeconfig)
+ if response, err := kor.GetUnusedConfigmaps(filterOptions, clientset, dynamicClient, outputFormat, opts); err != nil {
fmt.Println(err)
} else {
utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/pvc.go b/cmd/kor/pvc.go
index e29f9c61..28fccbb5 100644
--- a/cmd/kor/pvc.go
+++ b/cmd/kor/pvc.go
@@ -16,8 +16,9 @@ var pvcCmd = &cobra.Command{
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
clientset := kor.GetKubeClient(kubeconfig)
+ dynamicClient := kor.GetDynamicClient(kubeconfig)
- if response, err := kor.GetUnusedPvcs(filterOptions, clientset, outputFormat, opts); err != nil {
+ if response, err := kor.GetUnusedPvcs(filterOptions, clientset, dynamicClient, outputFormat, opts); err != nil {
fmt.Println(err)
} else {
utils.PrintLogo(outputFormat)
diff --git a/cmd/kor/secrets.go b/cmd/kor/secrets.go
index b484d4af..0126b4b4 100644
--- a/cmd/kor/secrets.go
+++ b/cmd/kor/secrets.go
@@ -16,8 +16,9 @@ var secretCmd = &cobra.Command{
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
clientset := kor.GetKubeClient(kubeconfig)
+ dynamicClient := kor.GetDynamicClient(kubeconfig)
- if response, err := kor.GetUnusedSecrets(filterOptions, clientset, outputFormat, opts); err != nil {
+ if response, err := kor.GetUnusedSecrets(filterOptions, clientset, dynamicClient, outputFormat, opts); err != nil {
fmt.Println(err)
} else {
utils.PrintLogo(outputFormat)
diff --git a/go.mod b/go.mod
index 14b77b90..0ecc60f3 100644
--- a/go.mod
+++ b/go.mod
@@ -1,10 +1,13 @@
module github.com/yonahd/kor
-go 1.24.0
+go 1.24.2
+
+toolchain go1.24.6
require (
+ github.com/argoproj/argo-workflows/v3 v3.7.1
github.com/fatih/color v1.18.0
- github.com/olekukonko/tablewriter v0.0.5
+ github.com/olekukonko/tablewriter v1.0.7
github.com/prometheus/client_golang v1.23.0
github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.20.1
@@ -12,7 +15,7 @@ require (
k8s.io/apiextensions-apiserver v0.33.3
k8s.io/apimachinery v0.33.3
k8s.io/client-go v0.33.3
- k8s.io/utils v0.0.0-20241210054802-24370beab758
+ k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979
sigs.k8s.io/yaml v1.6.0
)
@@ -20,38 +23,41 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
- github.com/emicklei/go-restful/v3 v3.11.0 // indirect
- github.com/fsnotify/fsnotify v1.8.0 // indirect
- github.com/fxamacker/cbor/v2 v2.7.0 // indirect
- github.com/go-logr/logr v1.4.2 // indirect
- github.com/go-openapi/jsonpointer v0.21.0 // indirect
- github.com/go-openapi/jsonreference v0.20.2 // indirect
- github.com/go-openapi/swag v0.23.0 // indirect
+ github.com/emicklei/go-restful/v3 v3.12.2 // indirect
+ github.com/fsnotify/fsnotify v1.9.0 // indirect
+ github.com/fxamacker/cbor/v2 v2.8.0 // indirect
+ github.com/go-logr/logr v1.4.3 // indirect
+ github.com/go-openapi/jsonpointer v0.21.1 // indirect
+ github.com/go-openapi/jsonreference v0.21.0 // indirect
+ github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
+ github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
- github.com/mailru/easyjson v0.7.7 // indirect
- github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mailru/easyjson v0.9.0 // indirect
+ github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/mattn/go-runewidth v0.0.14 // indirect
+ github.com/mattn/go-runewidth v0.0.16 // 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/pelletier/go-toml/v2 v2.2.3 // indirect
+ github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
- github.com/rivo/uniseg v0.4.4 // indirect
- github.com/sagikazarmark/locafero v0.7.0 // indirect
+ github.com/rivo/uniseg v0.4.7 // indirect
+ github.com/sagikazarmark/locafero v0.9.0 // indirect
+ github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
- github.com/spf13/afero v1.12.0 // indirect
- github.com/spf13/cast v1.7.1 // indirect
+ github.com/spf13/afero v1.14.0 // indirect
+ github.com/spf13/cast v1.9.2 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
@@ -62,14 +68,20 @@ require (
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.25.0 // indirect
- golang.org/x/time v0.9.0 // indirect
+ golang.org/x/time v0.11.0 // indirect
+ google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
+ google.golang.org/grpc v1.72.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
- sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
+ sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
- sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
+ sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
)
+
+replace github.com/olekukonko/tablewriter => github.com/olekukonko/tablewriter v0.0.5
diff --git a/go.sum b/go.sum
index 75b96b4f..a144c3e5 100644
--- a/go.sum
+++ b/go.sum
@@ -1,49 +1,72 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/argoproj/argo-workflows/v3 v3.7.1 h1:8xynztGc+tUuUq/SB9dHGp5EDI6UkSmuJeQPhHQrbek=
+github.com/argoproj/argo-workflows/v3 v3.7.1/go.mod h1:UleRW8WI3LHU/saPk9jEagm8zJFXWAw90iDVIZDA+UA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
-github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
+github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
-github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
-github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
-github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
-github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
-github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
-github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
-github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
-github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
-github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
-github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
-github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
-github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
-github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
+github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
+github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
+github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
+github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
+github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
+github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
+github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
+github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -54,25 +77,21 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
-github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
-github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
-github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
-github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
+github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
+github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
+github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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=
@@ -82,18 +101,21 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
-github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
-github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
+github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
+github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
@@ -101,19 +123,22 @@ github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGI
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
-github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
-github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
+github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
+github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
-github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
-github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
-github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
-github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
+github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
+github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
+github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
@@ -121,14 +146,10 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
@@ -148,23 +169,40 @@ go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
@@ -174,9 +212,13 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
-golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
-golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
+golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@@ -186,6 +228,24 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
+google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
+google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
+google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
+google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -195,9 +255,12 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8=
k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE=
k8s.io/apiextensions-apiserver v0.33.3 h1:qmOcAHN6DjfD0v9kxL5udB27SRP6SG/MTopmge3MwEs=
@@ -210,15 +273,15 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
-k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
-k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
-sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
-sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
+k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 h1:jgJW5IePPXLGB8e/1wvd0Ich9QE97RvvF3a8J3fP/Lg=
+k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
+sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
-sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
-sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
+sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
+sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
diff --git a/pkg/kor/all.go b/pkg/kor/all.go
index ab1ddbae..a8b3937e 100644
--- a/pkg/kor/all.go
+++ b/pkg/kor/all.go
@@ -26,8 +26,8 @@ type ResourceDiff struct {
diff []ResourceInfo
}
-func getUnusedCMs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options, opts common.Opts) ResourceDiff {
- cmDiff, err := processNamespaceCM(clientset, namespace, filterOpts, opts)
+func getUnusedCMs(clientset kubernetes.Interface, dynamicClient dynamic.Interface, namespace string, filterOpts *filters.Options, opts common.Opts) ResourceDiff {
+ cmDiff, err := processNamespaceCM(clientset, dynamicClient, namespace, filterOpts, opts)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "configmaps", namespace, err)
}
@@ -50,8 +50,8 @@ func getUnusedSVCs(clientset kubernetes.Interface, namespace string, filterOpts
return namespaceSVCDiff
}
-func getUnusedSecrets(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options, opts common.Opts) ResourceDiff {
- secretDiff, err := processNamespaceSecret(clientset, namespace, filterOpts, opts)
+func getUnusedSecrets(clientset kubernetes.Interface, dynamicClient dynamic.Interface, namespace string, filterOpts *filters.Options, opts common.Opts) ResourceDiff {
+ secretDiff, err := processNamespaceSecret(clientset, dynamicClient, namespace, filterOpts, opts)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "secrets", namespace, err)
}
@@ -146,8 +146,8 @@ func getUnusedHpas(clientset kubernetes.Interface, namespace string, filterOpts
return namespaceHpaDiff
}
-func getUnusedPvcs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options, opts common.Opts) ResourceDiff {
- pvcDiff, err := processNamespacePvcs(clientset, namespace, filterOpts, opts)
+func getUnusedPvcs(clientset kubernetes.Interface, dynamicClient dynamic.Interface, namespace string, filterOpts *filters.Options, opts common.Opts) ResourceDiff {
+ pvcDiff, err := processNamespacePvcs(clientset, dynamicClient, namespace, filterOpts, opts)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "pvcs", namespace, err)
}
@@ -302,21 +302,21 @@ func getUnusedRoleBindings(clientset kubernetes.Interface, namespace string, fil
return namespaceRoleBindingDiff
}
-func GetUnusedAllNamespaced(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts common.Opts) (string, error) {
+func GetUnusedAllNamespaced(filterOpts *filters.Options, clientset kubernetes.Interface, dynamicClient dynamic.Interface, outputFormat string, opts common.Opts) (string, error) {
resources := make(map[string]map[string][]ResourceInfo)
for _, namespace := range filterOpts.Namespaces(clientset) {
switch opts.GroupBy {
case "namespace":
resources[namespace] = make(map[string][]ResourceInfo)
- resources[namespace]["ConfigMap"] = getUnusedCMs(clientset, namespace, filterOpts, opts).diff
+ resources[namespace]["ConfigMap"] = getUnusedCMs(clientset, dynamicClient, namespace, filterOpts, opts).diff
resources[namespace]["Service"] = getUnusedSVCs(clientset, namespace, filterOpts, opts).diff
- resources[namespace]["Secret"] = getUnusedSecrets(clientset, namespace, filterOpts, opts).diff
+ resources[namespace]["Secret"] = getUnusedSecrets(clientset, dynamicClient, namespace, filterOpts, opts).diff
resources[namespace]["ServiceAccount"] = getUnusedServiceAccounts(clientset, namespace, filterOpts, opts).diff
resources[namespace]["Deployment"] = getUnusedDeployments(clientset, namespace, filterOpts, opts).diff
resources[namespace]["StatefulSet"] = getUnusedStatefulSets(clientset, namespace, filterOpts, opts).diff
resources[namespace]["Role"] = getUnusedRoles(clientset, namespace, filterOpts, opts).diff
resources[namespace]["Hpa"] = getUnusedHpas(clientset, namespace, filterOpts, opts).diff
- resources[namespace]["Pvc"] = getUnusedPvcs(clientset, namespace, filterOpts, opts).diff
+ resources[namespace]["Pvc"] = getUnusedPvcs(clientset, dynamicClient, namespace, filterOpts, opts).diff
resources[namespace]["Pod"] = getUnusedPods(clientset, namespace, filterOpts, opts).diff
resources[namespace]["Ingress"] = getUnusedIngresses(clientset, namespace, filterOpts, opts).diff
resources[namespace]["Pdb"] = getUnusedPdbs(clientset, namespace, filterOpts, opts).diff
@@ -326,15 +326,15 @@ func GetUnusedAllNamespaced(filterOpts *filters.Options, clientset kubernetes.In
resources[namespace]["NetworkPolicy"] = getUnusedNetworkPolicies(clientset, namespace, filterOpts, opts).diff
resources[namespace]["RoleBinding"] = getUnusedRoleBindings(clientset, namespace, filterOpts, opts).diff
case "resource":
- appendResources(resources, "ConfigMap", namespace, getUnusedCMs(clientset, namespace, filterOpts, opts).diff)
+ appendResources(resources, "ConfigMap", namespace, getUnusedCMs(clientset, dynamicClient, namespace, filterOpts, opts).diff)
appendResources(resources, "Service", namespace, getUnusedSVCs(clientset, namespace, filterOpts, opts).diff)
- appendResources(resources, "Secret", namespace, getUnusedSecrets(clientset, namespace, filterOpts, opts).diff)
+ appendResources(resources, "Secret", namespace, getUnusedSecrets(clientset, dynamicClient, namespace, filterOpts, opts).diff)
appendResources(resources, "ServiceAccount", namespace, getUnusedServiceAccounts(clientset, namespace, filterOpts, opts).diff)
appendResources(resources, "Deployment", namespace, getUnusedDeployments(clientset, namespace, filterOpts, opts).diff)
appendResources(resources, "StatefulSet", namespace, getUnusedStatefulSets(clientset, namespace, filterOpts, opts).diff)
appendResources(resources, "Role", namespace, getUnusedRoles(clientset, namespace, filterOpts, opts).diff)
appendResources(resources, "Hpa", namespace, getUnusedHpas(clientset, namespace, filterOpts, opts).diff)
- appendResources(resources, "Pvc", namespace, getUnusedPvcs(clientset, namespace, filterOpts, opts).diff)
+ appendResources(resources, "Pvc", namespace, getUnusedPvcs(clientset, dynamicClient, namespace, filterOpts, opts).diff)
appendResources(resources, "Pod", namespace, getUnusedPods(clientset, namespace, filterOpts, opts).diff)
appendResources(resources, "Ingress", namespace, getUnusedIngresses(clientset, namespace, filterOpts, opts).diff)
appendResources(resources, "Pdb", namespace, getUnusedPdbs(clientset, namespace, filterOpts, opts).diff)
@@ -410,12 +410,12 @@ func GetUnusedAllNonNamespaced(filterOpts *filters.Options, clientset kubernetes
func GetUnusedAll(filterOpts *filters.Options, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts common.Opts) (string, error) {
if NamespacedFlagUsed {
if opts.Namespaced {
- return GetUnusedAllNamespaced(filterOpts, clientset, outputFormat, opts)
+ return GetUnusedAllNamespaced(filterOpts, clientset, dynamicClient, outputFormat, opts)
}
return GetUnusedAllNonNamespaced(filterOpts, clientset, apiExtClient, dynamicClient, outputFormat, opts)
}
- unusedAllNamespaced, err := GetUnusedAllNamespaced(filterOpts, clientset, outputFormat, opts)
+ unusedAllNamespaced, err := GetUnusedAllNamespaced(filterOpts, clientset, dynamicClient, outputFormat, opts)
if err != nil {
fmt.Printf("err: %v\n", err)
}
diff --git a/pkg/kor/argo_workflow_validation.go b/pkg/kor/argo_workflow_validation.go
new file mode 100644
index 00000000..a0021d03
--- /dev/null
+++ b/pkg/kor/argo_workflow_validation.go
@@ -0,0 +1,180 @@
+package kor
+
+import (
+ "context"
+ "sync"
+
+ wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/client-go/dynamic"
+ "k8s.io/client-go/kubernetes"
+)
+
+var (
+ // Global state to track if Argo WorkflowTemplate CRD is available
+ argoWorkflowTemplateEnabled bool
+ argoWorkflowTemplateOnce sync.Once
+ workflowTemplateGVR = schema.GroupVersionResource{
+ Group: "argoproj.io",
+ Version: "v1alpha1",
+ Resource: "workflowtemplates",
+ }
+)
+
+// ArgoWorkflowTemplateResourceReferences holds references to resources used by WorkflowTemplates
+type ArgoWorkflowTemplateResourceReferences struct {
+ ConfigMaps []string
+ Secrets []string
+ PVCs []string
+}
+
+// ValidateArgoWorkflowTemplateAvailability checks once if the WorkflowTemplate CRD exists in the cluster
+func ValidateArgoWorkflowTemplateAvailability(dynamicClient dynamic.Interface) bool {
+ argoWorkflowTemplateOnce.Do(func() {
+ // Try to list WorkflowTemplates to check if the CRD exists
+ // Use limit=1 to minimize overhead - we just need to know if it exists
+ _, err := dynamicClient.Resource(workflowTemplateGVR).Namespace("").List(context.TODO(), metav1.ListOptions{Limit: 1})
+ argoWorkflowTemplateEnabled = (err == nil)
+ })
+ return argoWorkflowTemplateEnabled
+}
+
+// ValidateResourceReferencesFromArgoWorkflowTemplates scans WorkflowTemplates for resource references
+func ValidateResourceReferencesFromArgoWorkflowTemplates(clientset kubernetes.Interface, dynamicClient dynamic.Interface, namespace string) (*ArgoWorkflowTemplateResourceReferences, error) {
+ refs := &ArgoWorkflowTemplateResourceReferences{
+ ConfigMaps: make([]string, 0),
+ Secrets: make([]string, 0),
+ PVCs: make([]string, 0),
+ }
+
+ // If WorkflowTemplate CRD is not available, return empty references
+ if !ValidateArgoWorkflowTemplateAvailability(dynamicClient) {
+ return refs, nil
+ }
+
+ // List all WorkflowTemplates in the namespace
+ workflowTemplateList, err := dynamicClient.Resource(workflowTemplateGVR).Namespace(namespace).List(context.TODO(), metav1.ListOptions{})
+ if err != nil {
+ return nil, err
+ }
+
+ // Process each WorkflowTemplate
+ for _, item := range workflowTemplateList.Items {
+ // Convert unstructured to typed WorkflowTemplate
+ var workflowTemplate wfv1.WorkflowTemplate
+ if err := convertUnstructuredToWorkflowTemplate(&item, &workflowTemplate); err != nil {
+ // Skip invalid WorkflowTemplates but continue processing others
+ continue
+ }
+
+ extractResourceReferencesFromTypedWorkflowTemplate(&workflowTemplate, refs)
+ }
+
+ // Remove duplicates and sort
+ refs.ConfigMaps = RemoveDuplicatesAndSort(refs.ConfigMaps)
+ refs.Secrets = RemoveDuplicatesAndSort(refs.Secrets)
+ refs.PVCs = RemoveDuplicatesAndSort(refs.PVCs)
+
+ return refs, nil
+}
+
+// convertUnstructuredToWorkflowTemplate converts unstructured data to a typed WorkflowTemplate
+func convertUnstructuredToWorkflowTemplate(unstructuredItem *unstructured.Unstructured, workflowTemplate *wfv1.WorkflowTemplate) error {
+ // Convert unstructured to typed WorkflowTemplate using runtime converter
+ return runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredItem.Object, workflowTemplate)
+}
+
+// extractResourceReferencesFromTypedWorkflowTemplate extracts resource references from a typed WorkflowTemplate
+func extractResourceReferencesFromTypedWorkflowTemplate(wt *wfv1.WorkflowTemplate, refs *ArgoWorkflowTemplateResourceReferences) {
+ // Extract from synchronization semaphore
+ if wt.Spec.Synchronization != nil && wt.Spec.Synchronization.Semaphore != nil {
+ if wt.Spec.Synchronization.Semaphore.ConfigMapKeyRef != nil {
+ refs.ConfigMaps = append(refs.ConfigMaps, wt.Spec.Synchronization.Semaphore.ConfigMapKeyRef.Name)
+ }
+ }
+
+ // Extract from templates
+ for _, template := range wt.Spec.Templates {
+ extractResourceReferencesFromTemplate(&template, refs)
+ }
+
+ // Extract from global volumes
+ extractResourceReferencesFromVolumes(wt.Spec.Volumes, refs)
+}
+
+// extractResourceReferencesFromTemplate extracts resource references from a single template
+func extractResourceReferencesFromTemplate(template *wfv1.Template, refs *ArgoWorkflowTemplateResourceReferences) {
+ // Extract from container environment variables
+ if template.Container != nil {
+ extractResourceReferencesFromContainer(template.Container, refs)
+ }
+
+ // Extract from script environment variables
+ if template.Script != nil {
+ extractResourceReferencesFromContainer(&template.Script.Container, refs)
+ }
+
+ // Extract from volumes
+ extractResourceReferencesFromVolumes(template.Volumes, refs)
+}
+
+// extractResourceReferencesFromContainer extracts resource references from container environment variables
+func extractResourceReferencesFromContainer(container *corev1.Container, refs *ArgoWorkflowTemplateResourceReferences) {
+ // Extract from environment variables
+ for _, env := range container.Env {
+ if env.ValueFrom != nil {
+ if env.ValueFrom.ConfigMapKeyRef != nil {
+ refs.ConfigMaps = append(refs.ConfigMaps, env.ValueFrom.ConfigMapKeyRef.Name)
+ }
+ if env.ValueFrom.SecretKeyRef != nil {
+ refs.Secrets = append(refs.Secrets, env.ValueFrom.SecretKeyRef.Name)
+ }
+ }
+ }
+
+ // Extract from envFrom
+ for _, envFrom := range container.EnvFrom {
+ if envFrom.ConfigMapRef != nil {
+ refs.ConfigMaps = append(refs.ConfigMaps, envFrom.ConfigMapRef.Name)
+ }
+ if envFrom.SecretRef != nil {
+ refs.Secrets = append(refs.Secrets, envFrom.SecretRef.Name)
+ }
+ }
+}
+
+// extractResourceReferencesFromVolumes extracts resource references from volumes
+func extractResourceReferencesFromVolumes(volumes []corev1.Volume, refs *ArgoWorkflowTemplateResourceReferences) {
+ for _, volume := range volumes {
+ // ConfigMap volumes
+ if volume.ConfigMap != nil {
+ refs.ConfigMaps = append(refs.ConfigMaps, volume.ConfigMap.Name)
+ }
+
+ // Secret volumes
+ if volume.Secret != nil {
+ refs.Secrets = append(refs.Secrets, volume.Secret.SecretName)
+ }
+
+ // PVC volumes
+ if volume.PersistentVolumeClaim != nil {
+ refs.PVCs = append(refs.PVCs, volume.PersistentVolumeClaim.ClaimName)
+ }
+
+ // Projected volumes
+ if volume.Projected != nil {
+ for _, source := range volume.Projected.Sources {
+ if source.ConfigMap != nil {
+ refs.ConfigMaps = append(refs.ConfigMaps, source.ConfigMap.Name)
+ }
+ if source.Secret != nil {
+ refs.Secrets = append(refs.Secrets, source.Secret.Name)
+ }
+ }
+ }
+ }
+}
diff --git a/pkg/kor/argo_workflow_validation_test.go b/pkg/kor/argo_workflow_validation_test.go
new file mode 100644
index 00000000..f1bad001
--- /dev/null
+++ b/pkg/kor/argo_workflow_validation_test.go
@@ -0,0 +1,295 @@
+package kor
+
+import (
+ "testing"
+
+ wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ dynamicfake "k8s.io/client-go/dynamic/fake"
+ "k8s.io/client-go/kubernetes/fake"
+)
+
+func TestValidateArgoWorkflowTemplateAvailability(t *testing.T) {
+ scheme := runtime.NewScheme()
+
+ // Test case: WorkflowTemplate CRD doesn't exist (simulated by fake client)
+ dynamicClient := dynamicfake.NewSimpleDynamicClient(scheme)
+ enabled := ValidateArgoWorkflowTemplateAvailability(dynamicClient)
+
+ // Since we can't easily fake a CRD's existence in tests, we expect this to be false
+ if enabled {
+ t.Error("Expected ValidateArgoWorkflowTemplateAvailability to return false when CRD doesn't exist")
+ }
+}
+
+func TestValidateResourceReferencesFromArgoWorkflowTemplates_NoCRD(t *testing.T) {
+ clientset := fake.NewSimpleClientset()
+ scheme := runtime.NewScheme()
+ dynamicClient := dynamicfake.NewSimpleDynamicClient(scheme)
+
+ refs, err := ValidateResourceReferencesFromArgoWorkflowTemplates(clientset, dynamicClient, "test-namespace")
+ if err != nil {
+ t.Errorf("Expected no error, got: %v", err)
+ }
+
+ // When CRD doesn't exist, should return empty references
+ if len(refs.ConfigMaps) != 0 {
+ t.Errorf("Expected 0 ConfigMaps, got %d", len(refs.ConfigMaps))
+ }
+ if len(refs.Secrets) != 0 {
+ t.Errorf("Expected 0 Secrets, got %d", len(refs.Secrets))
+ }
+ if len(refs.PVCs) != 0 {
+ t.Errorf("Expected 0 PVCs, got %d", len(refs.PVCs))
+ }
+}
+
+func TestExtractResourceReferencesFromTypedWorkflowTemplate(t *testing.T) {
+ // Create a typed WorkflowTemplate with various resource references
+ wt := &wfv1.WorkflowTemplate{
+ Spec: wfv1.WorkflowSpec{
+ // Synchronization semaphore
+ Synchronization: &wfv1.Synchronization{
+ Semaphore: &wfv1.SemaphoreRef{
+ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: "workflow-semaphore-config",
+ },
+ Key: "workflow",
+ },
+ },
+ },
+ // Global volumes
+ Volumes: []corev1.Volume{
+ {
+ Name: "global-secret-volume",
+ VolumeSource: corev1.VolumeSource{
+ Secret: &corev1.SecretVolumeSource{
+ SecretName: "global-secret",
+ },
+ },
+ },
+ {
+ Name: "global-config-volume",
+ VolumeSource: corev1.VolumeSource{
+ ConfigMap: &corev1.ConfigMapVolumeSource{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: "global-config",
+ },
+ },
+ },
+ },
+ },
+ // Templates
+ Templates: []wfv1.Template{
+ {
+ Name: "test-template",
+ Container: &corev1.Container{
+ Name: "test-container",
+ Image: "busybox",
+ Env: []corev1.EnvVar{
+ {
+ Name: "CONFIG_VALUE",
+ ValueFrom: &corev1.EnvVarSource{
+ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: "app-config",
+ },
+ Key: "config-key",
+ },
+ },
+ },
+ {
+ Name: "SECRET_VALUE",
+ ValueFrom: &corev1.EnvVarSource{
+ SecretKeyRef: &corev1.SecretKeySelector{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: "app-secret",
+ },
+ Key: "secret-key",
+ },
+ },
+ },
+ },
+ EnvFrom: []corev1.EnvFromSource{
+ {
+ ConfigMapRef: &corev1.ConfigMapEnvSource{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: "env-config",
+ },
+ },
+ },
+ {
+ SecretRef: &corev1.SecretEnvSource{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: "env-secret",
+ },
+ },
+ },
+ },
+ },
+ Volumes: []corev1.Volume{
+ {
+ Name: "secret-volume",
+ VolumeSource: corev1.VolumeSource{
+ Secret: &corev1.SecretVolumeSource{
+ SecretName: "volume-secret",
+ },
+ },
+ },
+ {
+ Name: "config-volume",
+ VolumeSource: corev1.VolumeSource{
+ ConfigMap: &corev1.ConfigMapVolumeSource{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: "volume-config",
+ },
+ },
+ },
+ },
+ {
+ Name: "pvc-volume",
+ VolumeSource: corev1.VolumeSource{
+ PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
+ ClaimName: "test-pvc",
+ },
+ },
+ },
+ {
+ Name: "projected-volume",
+ VolumeSource: corev1.VolumeSource{
+ Projected: &corev1.ProjectedVolumeSource{
+ Sources: []corev1.VolumeProjection{
+ {
+ ConfigMap: &corev1.ConfigMapProjection{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: "projected-config",
+ },
+ },
+ },
+ {
+ Secret: &corev1.SecretProjection{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: "projected-secret",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ refs := &ArgoWorkflowTemplateResourceReferences{
+ ConfigMaps: make([]string, 0),
+ Secrets: make([]string, 0),
+ PVCs: make([]string, 0),
+ }
+
+ extractResourceReferencesFromTypedWorkflowTemplate(wt, refs)
+
+ expectedConfigMaps := []string{
+ "workflow-semaphore-config",
+ "global-config",
+ "app-config",
+ "env-config",
+ "volume-config",
+ "projected-config",
+ }
+
+ expectedSecrets := []string{
+ "global-secret",
+ "app-secret",
+ "env-secret",
+ "volume-secret",
+ "projected-secret",
+ }
+
+ expectedPVCs := []string{
+ "test-pvc",
+ }
+
+ // Check ConfigMaps
+ if len(refs.ConfigMaps) != len(expectedConfigMaps) {
+ t.Errorf("Expected %d ConfigMaps, got %d: %v", len(expectedConfigMaps), len(refs.ConfigMaps), refs.ConfigMaps)
+ }
+ for _, expected := range expectedConfigMaps {
+ found := false
+ for _, actual := range refs.ConfigMaps {
+ if actual == expected {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.Errorf("Expected ConfigMap %q not found in refs: %v", expected, refs.ConfigMaps)
+ }
+ }
+
+ // Check Secrets
+ if len(refs.Secrets) != len(expectedSecrets) {
+ t.Errorf("Expected %d Secrets, got %d: %v", len(expectedSecrets), len(refs.Secrets), refs.Secrets)
+ }
+ for _, expected := range expectedSecrets {
+ found := false
+ for _, actual := range refs.Secrets {
+ if actual == expected {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.Errorf("Expected Secret %q not found in refs: %v", expected, refs.Secrets)
+ }
+ }
+
+ // Check PVCs
+ if len(refs.PVCs) != len(expectedPVCs) {
+ t.Errorf("Expected %d PVCs, got %d: %v", len(expectedPVCs), len(refs.PVCs), refs.PVCs)
+ }
+ for _, expected := range expectedPVCs {
+ found := false
+ for _, actual := range refs.PVCs {
+ if actual == expected {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.Errorf("Expected PVC %q not found in refs: %v", expected, refs.PVCs)
+ }
+ }
+}
+
+func TestExtractResourceReferencesFromTypedWorkflowTemplate_Empty(t *testing.T) {
+ // Test with empty WorkflowTemplate
+ wt := &wfv1.WorkflowTemplate{
+ Spec: wfv1.WorkflowSpec{
+ Templates: []wfv1.Template{}, // Empty templates
+ },
+ }
+
+ refs := &ArgoWorkflowTemplateResourceReferences{
+ ConfigMaps: make([]string, 0),
+ Secrets: make([]string, 0),
+ PVCs: make([]string, 0),
+ }
+
+ extractResourceReferencesFromTypedWorkflowTemplate(wt, refs)
+
+ // Should be no references in an empty spec
+ if len(refs.ConfigMaps) != 0 {
+ t.Errorf("Expected 0 ConfigMaps, got %d", len(refs.ConfigMaps))
+ }
+ if len(refs.Secrets) != 0 {
+ t.Errorf("Expected 0 Secrets, got %d", len(refs.Secrets))
+ }
+ if len(refs.PVCs) != 0 {
+ t.Errorf("Expected 0 PVCs, got %d", len(refs.PVCs))
+ }
+}
diff --git a/pkg/kor/configmaps.go b/pkg/kor/configmaps.go
index d85affbf..2f0e8343 100644
--- a/pkg/kor/configmaps.go
+++ b/pkg/kor/configmaps.go
@@ -9,6 +9,7 @@ import (
"os"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
@@ -19,7 +20,7 @@ import (
//go:embed exceptions/configmaps/configmaps.json
var configMapsConfig []byte
-func retrieveUsedCM(clientset kubernetes.Interface, namespace string) ([]string, []string, []string, []string, []string, error) {
+func retrieveUsedCMFromPods(clientset kubernetes.Interface, namespace string) ([]string, []string, []string, []string, []string, error) {
var volumesCM []string
var envCM []string
var envFromCM []string
@@ -83,6 +84,14 @@ func retrieveUsedCM(clientset kubernetes.Interface, namespace string) ([]string,
return volumesCM, envCM, envFromCM, envFromContainerCM, envFromInitContainerCM, nil
}
+func retrieveUsedCMFromArgoWorkflowTemplates(clientset kubernetes.Interface, dynamicClient dynamic.Interface, namespace string) ([]string, error) {
+ refs, err := ValidateResourceReferencesFromArgoWorkflowTemplates(clientset, dynamicClient, namespace)
+ if err != nil {
+ return nil, err
+ }
+ return refs.ConfigMaps, nil
+}
+
func retrieveConfigMapNames(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, []string, error) {
configmaps, err := clientset.CoreV1().ConfigMaps(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
if err != nil {
@@ -112,11 +121,19 @@ func retrieveConfigMapNames(clientset kubernetes.Interface, namespace string, fi
return names, unusedConfigmapNames, nil
}
-func processNamespaceCM(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options, opts common.Opts) ([]ResourceInfo, error) {
- volumesCM, envCM, envFromCM, envFromContainerCM, envFromInitContainerCM, err := retrieveUsedCM(clientset, namespace)
+func processNamespaceCM(clientset kubernetes.Interface, dynamicClient dynamic.Interface, namespace string, filterOpts *filters.Options, opts common.Opts) ([]ResourceInfo, error) {
+ // Retrieve ConfigMaps referenced by Pods
+ volumesCM, envCM, envFromCM, envFromContainerCM, envFromInitContainerCM, err := retrieveUsedCMFromPods(clientset, namespace)
if err != nil {
return nil, err
}
+
+ // Retrieve ConfigMaps referenced by Argo WorkflowTemplates
+ argoCM, err := retrieveUsedCMFromArgoWorkflowTemplates(clientset, dynamicClient, namespace)
+ if err != nil {
+ return nil, err
+ }
+
config, err := unmarshalConfig(configMapsConfig)
if err != nil {
return nil, err
@@ -127,6 +144,7 @@ func processNamespaceCM(clientset kubernetes.Interface, namespace string, filter
envFromCM = RemoveDuplicatesAndSort(envFromCM)
envFromContainerCM = RemoveDuplicatesAndSort(envFromContainerCM)
envFromInitContainerCM = RemoveDuplicatesAndSort(envFromInitContainerCM)
+ argoCM = RemoveDuplicatesAndSort(argoCM)
configMapNames, unusedConfigmapNames, err := retrieveConfigMapNames(clientset, namespace, filterOpts)
if err != nil {
@@ -140,6 +158,7 @@ func processNamespaceCM(clientset kubernetes.Interface, namespace string, filter
envFromCM,
envFromContainerCM,
envFromInitContainerCM,
+ argoCM,
}
for _, slice := range slicesToAppend {
@@ -175,10 +194,10 @@ func processNamespaceCM(clientset kubernetes.Interface, namespace string, filter
return diff, nil
}
-func GetUnusedConfigmaps(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts common.Opts) (string, error) {
+func GetUnusedConfigmaps(filterOpts *filters.Options, clientset kubernetes.Interface, dynamicClient dynamic.Interface, outputFormat string, opts common.Opts) (string, error) {
resources := make(map[string]map[string][]ResourceInfo)
for _, namespace := range filterOpts.Namespaces(clientset) {
- diff, err := processNamespaceCM(clientset, namespace, filterOpts, opts)
+ diff, err := processNamespaceCM(clientset, dynamicClient, namespace, filterOpts, opts)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err)
continue
diff --git a/pkg/kor/multi.go b/pkg/kor/multi.go
index f3dbfbad..b9d6d44b 100644
--- a/pkg/kor/multi.go
+++ b/pkg/kor/multi.go
@@ -82,18 +82,18 @@ func retrieveNoNamespaceDiff(clientset kubernetes.Interface, apiExtClient apiext
return noNamespaceDiff, clearedResourceList
}
-func retrieveNamespaceDiffs(clientset kubernetes.Interface, namespace string, resourceList []string, filterOpts *filters.Options, opts common.Opts) []ResourceDiff {
+func retrieveNamespaceDiffs(clientset kubernetes.Interface, dynamicClient dynamic.Interface, namespace string, resourceList []string, filterOpts *filters.Options, opts common.Opts) []ResourceDiff {
var allDiffs []ResourceDiff
for _, resource := range resourceList {
var diffResult ResourceDiff
canonicalType := getCanonicalResourceType(resource)
switch canonicalType {
case "configmap":
- diffResult = getUnusedCMs(clientset, namespace, filterOpts, opts)
+ diffResult = getUnusedCMs(clientset, dynamicClient, namespace, filterOpts, opts)
case "service":
diffResult = getUnusedSVCs(clientset, namespace, filterOpts, opts)
case "secret":
- diffResult = getUnusedSecrets(clientset, namespace, filterOpts, opts)
+ diffResult = getUnusedSecrets(clientset, dynamicClient, namespace, filterOpts, opts)
case "serviceaccount":
diffResult = getUnusedServiceAccounts(clientset, namespace, filterOpts, opts)
case "deployment":
@@ -105,7 +105,7 @@ func retrieveNamespaceDiffs(clientset kubernetes.Interface, namespace string, re
case "horizontalpodautoscaler":
diffResult = getUnusedHpas(clientset, namespace, filterOpts, opts)
case "persistentvolumeclaim":
- diffResult = getUnusedPvcs(clientset, namespace, filterOpts, opts)
+ diffResult = getUnusedPvcs(clientset, dynamicClient, namespace, filterOpts, opts)
case "ingress":
diffResult = getUnusedIngresses(clientset, namespace, filterOpts, opts)
case "poddisruptionbudget":
@@ -160,7 +160,7 @@ func GetUnusedMulti(resourceNames string, filterOpts *filters.Options, clientset
}
for _, namespace := range namespaces {
- allDiffs := retrieveNamespaceDiffs(clientset, namespace, resourceList, filterOpts, opts)
+ allDiffs := retrieveNamespaceDiffs(clientset, dynamicClient, namespace, resourceList, filterOpts, opts)
if opts.GroupBy == "namespace" {
resources[namespace] = make(map[string][]ResourceInfo)
}
diff --git a/pkg/kor/pvc.go b/pkg/kor/pvc.go
index 80861eb2..67d33682 100644
--- a/pkg/kor/pvc.go
+++ b/pkg/kor/pvc.go
@@ -8,6 +8,7 @@ import (
"os"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
@@ -15,7 +16,7 @@ import (
"github.com/yonahd/kor/pkg/filters"
)
-func retrieveUsedPvcs(clientset kubernetes.Interface, namespace string) ([]string, error) {
+func retrieveUsedPvcsFromPods(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)
@@ -38,7 +39,15 @@ func retrieveUsedPvcs(clientset kubernetes.Interface, namespace string) ([]strin
return usedPvcs, err
}
-func processNamespacePvcs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options, opts common.Opts) ([]ResourceInfo, error) {
+func retrieveUsedPvcsFromArgoWorkflowTemplates(clientset kubernetes.Interface, dynamicClient dynamic.Interface, namespace string) ([]string, error) {
+ refs, err := ValidateResourceReferencesFromArgoWorkflowTemplates(clientset, dynamicClient, namespace)
+ if err != nil {
+ return nil, err
+ }
+ return refs.PVCs, nil
+}
+
+func processNamespacePvcs(clientset kubernetes.Interface, dynamicClient dynamic.Interface, namespace string, filterOpts *filters.Options, opts common.Opts) ([]ResourceInfo, error) {
pvcs, err := clientset.CoreV1().PersistentVolumeClaims(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
if err != nil {
return nil, err
@@ -64,13 +73,24 @@ func processNamespacePvcs(clientset kubernetes.Interface, namespace string, filt
pvcNames = append(pvcNames, pvc.Name)
}
- usedPvcs, err := retrieveUsedPvcs(clientset, namespace)
+ // Retrieve PVCs referenced by Pods
+ podPvcs, err := retrieveUsedPvcsFromPods(clientset, namespace)
if err != nil {
return nil, err
}
+ // Retrieve PVCs referenced by Argo WorkflowTemplates
+ argoPvcs, err := retrieveUsedPvcsFromArgoWorkflowTemplates(clientset, dynamicClient, namespace)
+ if err != nil {
+ return nil, err
+ }
+
+ // Combine all used PVCs
+ allUsedPvcs := append(podPvcs, argoPvcs...)
+ allUsedPvcs = RemoveDuplicatesAndSort(allUsedPvcs)
+
var diff []ResourceInfo
- for _, name := range CalculateResourceDifference(usedPvcs, pvcNames) {
+ for _, name := range CalculateResourceDifference(allUsedPvcs, pvcNames) {
reason := "PVC is not in use"
diff = append(diff, ResourceInfo{Name: name, Reason: reason})
}
@@ -88,10 +108,10 @@ func processNamespacePvcs(clientset kubernetes.Interface, namespace string, filt
return diff, nil
}
-func GetUnusedPvcs(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts common.Opts) (string, error) {
+func GetUnusedPvcs(filterOpts *filters.Options, clientset kubernetes.Interface, dynamicClient dynamic.Interface, outputFormat string, opts common.Opts) (string, error) {
resources := make(map[string]map[string][]ResourceInfo)
for _, namespace := range filterOpts.Namespaces(clientset) {
- diff, err := processNamespacePvcs(clientset, namespace, filterOpts, opts)
+ diff, err := processNamespacePvcs(clientset, dynamicClient, namespace, filterOpts, opts)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err)
continue
diff --git a/pkg/kor/pvc_test.go b/pkg/kor/pvc_test.go
index 47e2c319..18d364af 100644
--- a/pkg/kor/pvc_test.go
+++ b/pkg/kor/pvc_test.go
@@ -120,7 +120,7 @@ func TestGetUnusedPvcsStructured(t *testing.T) {
GroupBy: "namespace",
}
- output, err := GetUnusedPvcs(&filters.Options{}, clientset, "json", opts)
+ output, err := GetUnusedPvcs(&filters.Options{}, clientset, nil, "json", opts)
if err != nil {
t.Fatalf("Error calling GetUnusedPvcsStructured: %v", err)
}
diff --git a/pkg/kor/secrets.go b/pkg/kor/secrets.go
index 098e96f8..b2be1d60 100644
--- a/pkg/kor/secrets.go
+++ b/pkg/kor/secrets.go
@@ -9,6 +9,7 @@ import (
"os"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
"k8s.io/utils/strings/slices"
@@ -45,7 +46,7 @@ func retrieveIngressTLS(clientset kubernetes.Interface, namespace string) ([]str
}
-func retrieveUsedSecret(clientset kubernetes.Interface, namespace string) ([]string, []string, []string, []string, []string, []string, error) {
+func retrieveUsedSecretFromPods(clientset kubernetes.Interface, namespace string) ([]string, []string, []string, []string, []string, []string, error) {
var envSecrets []string
var envSecrets2 []string
var volumeSecrets []string
@@ -114,6 +115,14 @@ func retrieveUsedSecret(clientset kubernetes.Interface, namespace string) ([]str
return envSecrets, envSecrets2, volumeSecrets, initContainerEnvSecrets, pullSecrets, tlsSecrets, nil
}
+func retrieveUsedSecretFromArgoWorkflowTemplates(clientset kubernetes.Interface, dynamicClient dynamic.Interface, namespace string) ([]string, error) {
+ refs, err := ValidateResourceReferencesFromArgoWorkflowTemplates(clientset, dynamicClient, namespace)
+ if err != nil {
+ return nil, err
+ }
+ return refs.Secrets, nil
+}
+
func retrieveSecretNames(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, []string, error) {
secrets, err := clientset.CoreV1().Secrets(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels})
if err != nil {
@@ -158,8 +167,15 @@ func retrieveSecretNames(clientset kubernetes.Interface, namespace string, filte
return names, unusedSecretNames, nil
}
-func processNamespaceSecret(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options, opts common.Opts) ([]ResourceInfo, error) {
- envSecrets, envSecrets2, volumeSecrets, initContainerEnvSecrets, pullSecrets, tlsSecrets, err := retrieveUsedSecret(clientset, namespace)
+func processNamespaceSecret(clientset kubernetes.Interface, dynamicClient dynamic.Interface, namespace string, filterOpts *filters.Options, opts common.Opts) ([]ResourceInfo, error) {
+ // Retrieve Secrets referenced by Pods
+ envSecrets, envSecrets2, volumeSecrets, initContainerEnvSecrets, pullSecrets, tlsSecrets, err := retrieveUsedSecretFromPods(clientset, namespace)
+ if err != nil {
+ return nil, err
+ }
+
+ // Retrieve Secrets referenced by Argo WorkflowTemplates
+ argoSecrets, err := retrieveUsedSecretFromArgoWorkflowTemplates(clientset, dynamicClient, namespace)
if err != nil {
return nil, err
}
@@ -170,6 +186,7 @@ func processNamespaceSecret(clientset kubernetes.Interface, namespace string, fi
initContainerEnvSecrets = RemoveDuplicatesAndSort(initContainerEnvSecrets)
pullSecrets = RemoveDuplicatesAndSort(pullSecrets)
tlsSecrets = RemoveDuplicatesAndSort(tlsSecrets)
+ argoSecrets = RemoveDuplicatesAndSort(argoSecrets)
secretNames, unusedSecretNames, err := retrieveSecretNames(clientset, namespace, filterOpts)
if err != nil {
@@ -184,6 +201,7 @@ func processNamespaceSecret(clientset kubernetes.Interface, namespace string, fi
pullSecrets,
tlsSecrets,
initContainerEnvSecrets,
+ argoSecrets,
}
for _, slice := range slicesToAppend {
@@ -211,10 +229,10 @@ func processNamespaceSecret(clientset kubernetes.Interface, namespace string, fi
}
-func GetUnusedSecrets(filterOpts *filters.Options, clientset kubernetes.Interface, outputFormat string, opts common.Opts) (string, error) {
+func GetUnusedSecrets(filterOpts *filters.Options, clientset kubernetes.Interface, dynamicClient dynamic.Interface, outputFormat string, opts common.Opts) (string, error) {
resources := make(map[string]map[string][]ResourceInfo)
for _, namespace := range filterOpts.Namespaces(clientset) {
- diff, err := processNamespaceSecret(clientset, namespace, filterOpts, opts)
+ diff, err := processNamespaceSecret(clientset, dynamicClient, namespace, filterOpts, opts)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err)
continue
diff --git a/pkg/kor/secrets_test.go b/pkg/kor/secrets_test.go
index 15cf8895..b1925130 100644
--- a/pkg/kor/secrets_test.go
+++ b/pkg/kor/secrets_test.go
@@ -306,7 +306,7 @@ func TestGetUnusedSecretsStructured(t *testing.T) {
GroupBy: "namespace",
}
- output, err := GetUnusedSecrets(&filters.Options{}, clientset, "json", opts)
+ output, err := GetUnusedSecrets(&filters.Options{}, clientset, nil, "json", opts)
if err != nil {
t.Fatalf("Error calling GetUnusedSecretsStructured: %v", err)
}