From 03f064afc8d0496d0fe1cd58c5ab3e3dae0f78ea Mon Sep 17 00:00:00 2001 From: Yonah Dissen Date: Sun, 30 Jul 2023 12:24:30 +0300 Subject: [PATCH 1/7] Support json return in roles command --- cmd/kor/roles.go | 8 ++++++-- cmd/kor/root.go | 1 + pkg/kor/roles.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/cmd/kor/roles.go b/cmd/kor/roles.go index 415970ba..103e33c1 100644 --- a/cmd/kor/roles.go +++ b/cmd/kor/roles.go @@ -10,12 +10,16 @@ var roleCmd = &cobra.Command{ Short: "Gets unused roles", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - kor.GetUnusedRoles(namespace) - + if outputFormat == "json" { + kor.GetUnusedRolesJSON(namespace) + } else { + kor.GetUnusedRoles(namespace) + } }, } func init() { roleCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Namespace to run on") + roleCmd.PersistentFlags().StringVar(&outputFormat, "output", "table", "Output format (table or json)") rootCmd.AddCommand(roleCmd) } diff --git a/cmd/kor/root.go b/cmd/kor/root.go index 84978976..52cf51b5 100644 --- a/cmd/kor/root.go +++ b/cmd/kor/root.go @@ -17,6 +17,7 @@ var rootCmd = &cobra.Command{ } var namespace string +var outputFormat string func Execute() { diff --git a/pkg/kor/roles.go b/pkg/kor/roles.go index 6c4f2649..d8bcea5c 100644 --- a/pkg/kor/roles.go +++ b/pkg/kor/roles.go @@ -2,6 +2,7 @@ package kor import ( "context" + "encoding/json" "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -101,3 +102,33 @@ func GetUnusedRoles(namespace string) { fmt.Println() } } + +func GetUnusedRolesJSON(namespace string) (string, error) { + var kubeClient *kubernetes.Clientset + var namespaces []string + + kubeClient = GetKubeClient() + + namespaces = SetNamespaceList(namespace, kubeClient) + + // Create a map to store the unused roles for each namespace + roleMap := make(map[string][]string) + + for _, namespace := range namespaces { + diff, err := processNamespaceRoles(kubeClient, namespace) + if err != nil { + return "", fmt.Errorf("failed to process namespace %s: %v", namespace, err) + } + + // Store the unused roles in the map + roleMap[namespace] = diff + } + + // Convert the map to JSON + jsonResponse, err := json.MarshalIndent(roleMap, "", " ") + if err != nil { + return "", err + } + + return string(jsonResponse), nil +} From e20507120474cbc243f2395a270da0bfa1f7f352 Mon Sep 17 00:00:00 2001 From: Yonah Dissen Date: Sun, 30 Jul 2023 13:13:50 +0300 Subject: [PATCH 2/7] Support json return in cms command --- cmd/kor/configmaps.go | 6 +++++- pkg/kor/confimgmaps.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/cmd/kor/configmaps.go b/cmd/kor/configmaps.go index 490dfd84..24a148eb 100644 --- a/cmd/kor/configmaps.go +++ b/cmd/kor/configmaps.go @@ -11,7 +11,11 @@ var configmapCmd = &cobra.Command{ Short: "Gets unused configmaps", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { - kor.GetUnusedConfigmaps(namespace) + if outputFormat == "json" { + kor.GetUnusedConfigmapsJSON(namespace) + } else { + kor.GetUnusedConfigmaps(namespace) + } }, } diff --git a/pkg/kor/confimgmaps.go b/pkg/kor/confimgmaps.go index 289f9f78..6a2c0038 100644 --- a/pkg/kor/confimgmaps.go +++ b/pkg/kor/confimgmaps.go @@ -2,6 +2,7 @@ package kor import ( "context" + "encoding/json" "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -140,3 +141,33 @@ func GetUnusedConfigmaps(namespace string) { fmt.Println() } } + +func GetUnusedConfigmapsJSON(namespace string) (string, error) { + var kubeClient *kubernetes.Clientset + var namespaces []string + + kubeClient = GetKubeClient() + + namespaces = SetNamespaceList(namespace, kubeClient) + + // Create a map to store the unused roles for each namespace + CMMap := make(map[string][]string) + + for _, namespace := range namespaces { + diff, err := processNamespaceCM(kubeClient, namespace) + if err != nil { + return "", fmt.Errorf("failed to process namespace %s: %v", namespace, err) + } + + // Store the unused roles in the map + CMMap[namespace] = diff + } + + // Convert the map to JSON + jsonResponse, err := json.MarshalIndent(CMMap, "", " ") + if err != nil { + return "", err + } + + return string(jsonResponse), nil +} From e5a402a083480b5ced9da942f58452e7da9b7675 Mon Sep 17 00:00:00 2001 From: Yonah Dissen Date: Sun, 30 Jul 2023 16:09:07 +0300 Subject: [PATCH 3/7] Support json return in all command --- pkg/kor/all.go | 60 ++++++++++++++++++++++++++++++++++++++++++ pkg/kor/confimgmaps.go | 15 ++++++----- pkg/kor/roles.go | 15 ++++++----- 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/pkg/kor/all.go b/pkg/kor/all.go index a3134507..dfcdcad3 100644 --- a/pkg/kor/all.go +++ b/pkg/kor/all.go @@ -1,11 +1,17 @@ package kor import ( + "encoding/json" "fmt" "k8s.io/client-go/kubernetes" "os" ) +type GetUnusedResourceJSONResponse struct { + ResourceType string `json:"resourceType"` + Namespaces map[string][]string `json:"namespaces"` +} + type ResourceDiff struct { resourceType string diff []string @@ -102,3 +108,57 @@ func GetUnusedAll(namespace string) { fmt.Println() } } + +func GetUnusedAllJSON(namespace string) (string, error) { + var kubeClient *kubernetes.Clientset + var namespaces []string + + kubeClient = GetKubeClient() + + namespaces = SetNamespaceList(namespace, kubeClient) + + // Create the JSON response object + response := GetUnusedResourceJSONResponse{ + Namespaces: make(map[string][]string), + } + + for _, namespace := range namespaces { + var allDiffs []ResourceDiff + + namespaceCMDiff := getUnusedCMs(kubeClient, namespace) + allDiffs = append(allDiffs, namespaceCMDiff) + + namespaceSVCDiff := getUnusedSVCs(kubeClient, namespace) + allDiffs = append(allDiffs, namespaceSVCDiff) + + namespaceSecretDiff := getUnusedSecrets(kubeClient, namespace) + allDiffs = append(allDiffs, namespaceSecretDiff) + + namespaceSADiff := getUnusedServiceAccounts(kubeClient, namespace) + allDiffs = append(allDiffs, namespaceSADiff) + + namespaceDeploymentDiff := getUnusedDeployments(kubeClient, namespace) + allDiffs = append(allDiffs, namespaceDeploymentDiff) + + namespaceStatefulsetDiff := getUnusedStatefulsets(kubeClient, namespace) + allDiffs = append(allDiffs, namespaceStatefulsetDiff) + + namespaceRoleDiff := getUnusedRoles(kubeClient, namespace) + allDiffs = append(allDiffs, namespaceRoleDiff) + + // Store the unused resources under the namespace in the JSON response + var unusedResources []string + for _, diff := range allDiffs { + unusedResources = append(unusedResources, diff.diff...) + } + response.Namespaces[namespace] = unusedResources + } + + // Convert the response object to JSON + jsonResponse, err := json.MarshalIndent(response, "", " ") + if err != nil { + return "", err + } + + return string(jsonResponse), nil +} diff --git a/pkg/kor/confimgmaps.go b/pkg/kor/confimgmaps.go index 6a2c0038..3cc05548 100644 --- a/pkg/kor/confimgmaps.go +++ b/pkg/kor/confimgmaps.go @@ -150,8 +150,11 @@ func GetUnusedConfigmapsJSON(namespace string) (string, error) { namespaces = SetNamespaceList(namespace, kubeClient) - // Create a map to store the unused roles for each namespace - CMMap := make(map[string][]string) + // Create the JSON response object + response := GetUnusedResourceJSONResponse{ + ResourceType: "Configmaps", + Namespaces: make(map[string][]string), + } for _, namespace := range namespaces { diff, err := processNamespaceCM(kubeClient, namespace) @@ -159,12 +162,12 @@ func GetUnusedConfigmapsJSON(namespace string) (string, error) { return "", fmt.Errorf("failed to process namespace %s: %v", namespace, err) } - // Store the unused roles in the map - CMMap[namespace] = diff + // Store the unused roles under the namespace in the JSON response + response.Namespaces[namespace] = diff } - // Convert the map to JSON - jsonResponse, err := json.MarshalIndent(CMMap, "", " ") + // Convert the response object to JSON + jsonResponse, err := json.MarshalIndent(response, "", " ") if err != nil { return "", err } diff --git a/pkg/kor/roles.go b/pkg/kor/roles.go index d8bcea5c..6cc93157 100644 --- a/pkg/kor/roles.go +++ b/pkg/kor/roles.go @@ -111,8 +111,11 @@ func GetUnusedRolesJSON(namespace string) (string, error) { namespaces = SetNamespaceList(namespace, kubeClient) - // Create a map to store the unused roles for each namespace - roleMap := make(map[string][]string) + // Create the JSON response object + response := GetUnusedResourceJSONResponse{ + ResourceType: "Roles", + Namespaces: make(map[string][]string), + } for _, namespace := range namespaces { diff, err := processNamespaceRoles(kubeClient, namespace) @@ -120,12 +123,12 @@ func GetUnusedRolesJSON(namespace string) (string, error) { return "", fmt.Errorf("failed to process namespace %s: %v", namespace, err) } - // Store the unused roles in the map - roleMap[namespace] = diff + // Store the unused roles under the namespace in the JSON response + response.Namespaces[namespace] = diff } - // Convert the map to JSON - jsonResponse, err := json.MarshalIndent(roleMap, "", " ") + // Convert the response object to JSON + jsonResponse, err := json.MarshalIndent(response, "", " ") if err != nil { return "", err } From 6fd5cd0dee25f5b67390255c6539523a141853b2 Mon Sep 17 00:00:00 2001 From: Yonah Dissen Date: Sun, 30 Jul 2023 16:25:20 +0300 Subject: [PATCH 4/7] Support json return in all command --- cmd/kor/all.go | 7 ++++++- cmd/kor/configmaps.go | 1 + pkg/kor/all.go | 13 ++++++------- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/cmd/kor/all.go b/cmd/kor/all.go index 40c86e09..2bc258b8 100644 --- a/cmd/kor/all.go +++ b/cmd/kor/all.go @@ -10,12 +10,17 @@ var allCmd = &cobra.Command{ Short: "Gets unused resources", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { - kor.GetUnusedAll(namespace) + if outputFormat == "json" { + kor.GetUnusedAllJSON(namespace) + } else { + kor.GetUnusedAll(namespace) + } }, } func init() { allCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Namespace to run on") + allCmd.PersistentFlags().StringVar(&outputFormat, "output", "table", "Output format (table or json)") rootCmd.AddCommand(allCmd) } diff --git a/cmd/kor/configmaps.go b/cmd/kor/configmaps.go index 24a148eb..4004e852 100644 --- a/cmd/kor/configmaps.go +++ b/cmd/kor/configmaps.go @@ -22,5 +22,6 @@ var configmapCmd = &cobra.Command{ func init() { configmapCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Namespace to run on") + configmapCmd.PersistentFlags().StringVar(&outputFormat, "output", "table", "Output format (table or json)") rootCmd.AddCommand(configmapCmd) } diff --git a/pkg/kor/all.go b/pkg/kor/all.go index dfcdcad3..153e6dad 100644 --- a/pkg/kor/all.go +++ b/pkg/kor/all.go @@ -118,9 +118,7 @@ func GetUnusedAllJSON(namespace string) (string, error) { namespaces = SetNamespaceList(namespace, kubeClient) // Create the JSON response object - response := GetUnusedResourceJSONResponse{ - Namespaces: make(map[string][]string), - } + response := make(map[string]map[string][]string) for _, namespace := range namespaces { var allDiffs []ResourceDiff @@ -146,12 +144,12 @@ func GetUnusedAllJSON(namespace string) (string, error) { namespaceRoleDiff := getUnusedRoles(kubeClient, namespace) allDiffs = append(allDiffs, namespaceRoleDiff) - // Store the unused resources under the namespace in the JSON response - var unusedResources []string + // Store the unused resources for each resource type in the JSON response + resourceMap := make(map[string][]string) for _, diff := range allDiffs { - unusedResources = append(unusedResources, diff.diff...) + resourceMap[diff.resourceType] = diff.diff } - response.Namespaces[namespace] = unusedResources + response[namespace] = resourceMap } // Convert the response object to JSON @@ -162,3 +160,4 @@ func GetUnusedAllJSON(namespace string) (string, error) { return string(jsonResponse), nil } +W From 74938063817d2d3fcc7373dd652686f5a13333f5 Mon Sep 17 00:00:00 2001 From: Yonah Dissen Date: Sun, 30 Jul 2023 16:28:22 +0300 Subject: [PATCH 5/7] Small fix Readme --- README.md | 2 +- pkg/kor/all.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d1efd0e4..e7a53727 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ kor [subcommand] --help | Services | Services with no endpoints | | | Deployments | Deployments with 0 Replicas | | | ServiceAccounts | ServiceAccounts unused by pods
ServiceAccounts unused by roleBinding or clusterRoleBinding | | -| Statefulsets | Statefulsets with no endpoints | | +| Statefulsets | Statefulsets with 0 Replicas | | | Roles | Roles not used in roleBinding | | diff --git a/pkg/kor/all.go b/pkg/kor/all.go index 153e6dad..7f1d9bc8 100644 --- a/pkg/kor/all.go +++ b/pkg/kor/all.go @@ -160,4 +160,3 @@ func GetUnusedAllJSON(namespace string) (string, error) { return string(jsonResponse), nil } -W From 577d8997934df337b869aff676991ffdda4a813b Mon Sep 17 00:00:00 2001 From: Yonah Dissen Date: Mon, 31 Jul 2023 09:39:25 +0300 Subject: [PATCH 6/7] reformat json response --- pkg/kor/confimgmaps.go | 18 ++++++------------ pkg/kor/roles.go | 25 ++++++++++--------------- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/pkg/kor/confimgmaps.go b/pkg/kor/confimgmaps.go index 3cc05548..78e90e85 100644 --- a/pkg/kor/confimgmaps.go +++ b/pkg/kor/confimgmaps.go @@ -147,26 +147,20 @@ func GetUnusedConfigmapsJSON(namespace string) (string, error) { var namespaces []string kubeClient = GetKubeClient() - namespaces = SetNamespaceList(namespace, kubeClient) - - // Create the JSON response object - response := GetUnusedResourceJSONResponse{ - ResourceType: "Configmaps", - Namespaces: make(map[string][]string), - } + response := make(map[string]map[string][]string) for _, namespace := range namespaces { diff, err := processNamespaceCM(kubeClient, namespace) if err != nil { - return "", fmt.Errorf("failed to process namespace %s: %v", namespace, err) + fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err) + continue } - - // Store the unused roles under the namespace in the JSON response - response.Namespaces[namespace] = diff + resourceMap := make(map[string][]string) + resourceMap["configmap"] = diff + response[namespace] = resourceMap } - // Convert the response object to JSON jsonResponse, err := json.MarshalIndent(response, "", " ") if err != nil { return "", err diff --git a/pkg/kor/roles.go b/pkg/kor/roles.go index 6cc93157..7994a958 100644 --- a/pkg/kor/roles.go +++ b/pkg/kor/roles.go @@ -88,7 +88,6 @@ func GetUnusedRoles(namespace string) { var namespaces []string kubeClient = GetKubeClient() - namespaces = SetNamespaceList(namespace, kubeClient) for _, namespace := range namespaces { @@ -108,26 +107,22 @@ func GetUnusedRolesJSON(namespace string) (string, error) { var namespaces []string kubeClient = GetKubeClient() - namespaces = SetNamespaceList(namespace, kubeClient) - - // Create the JSON response object - response := GetUnusedResourceJSONResponse{ - ResourceType: "Roles", - Namespaces: make(map[string][]string), - } + response := make(map[string]map[string][]string) for _, namespace := range namespaces { - diff, err := processNamespaceRoles(kubeClient, namespace) - if err != nil { - return "", fmt.Errorf("failed to process namespace %s: %v", namespace, err) - } + var allDiffs []ResourceDiff - // Store the unused roles under the namespace in the JSON response - response.Namespaces[namespace] = diff + namespaceRoleDiff := getUnusedRoles(kubeClient, namespace) + allDiffs = append(allDiffs, namespaceRoleDiff) + + resourceMap := make(map[string][]string) + for _, diff := range allDiffs { + resourceMap[diff.resourceType] = diff.diff + } + response[namespace] = resourceMap } - // Convert the response object to JSON jsonResponse, err := json.MarshalIndent(response, "", " ") if err != nil { return "", err From 585191215e99e440341e471d3499d4d4a1fd4da3 Mon Sep 17 00:00:00 2001 From: Yonah Dissen Date: Tue, 1 Aug 2023 10:40:13 +0300 Subject: [PATCH 7/7] support optional kubeconfig flag --- README.md | 35 ++++++++++++++++++++++++++++++++++- cmd/kor/all.go | 5 +++-- cmd/kor/configmaps.go | 5 +++-- cmd/kor/deployments.go | 8 +++++++- cmd/kor/roles.go | 5 +++-- cmd/kor/root.go | 2 +- cmd/kor/secrets.go | 8 +++++++- cmd/kor/serviceaccounts.go | 8 +++++++- cmd/kor/services.go | 8 +++++++- cmd/kor/statefulsets.go | 8 +++++++- pkg/kor/all.go | 8 ++++---- pkg/kor/confimgmaps.go | 10 +++++----- pkg/kor/deployments.go | 32 ++++++++++++++++++++++++++++++-- pkg/kor/kor.go | 7 ++++--- pkg/kor/roles.go | 22 ++++++++++------------ pkg/kor/secrets.go | 32 ++++++++++++++++++++++++++++++-- pkg/kor/serviceaccounts.go | 32 ++++++++++++++++++++++++++++++-- pkg/kor/services.go | 32 ++++++++++++++++++++++++++++++-- pkg/kor/statefulsets.go | 32 ++++++++++++++++++++++++++++++-- 19 files changed, 252 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index e7a53727..895ca5ba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Kor - Kubernetes Orphaned Resources Finder -Kor is a CLI tool to discover unused Kubernetes resources. Currently, Kor can identify and list unused: +Kor is a tool to discover unused Kubernetes resources. Currently, Kor can identify and list unused: - ConfigMaps - Secrets. - Services @@ -28,6 +28,14 @@ Kor provides various subcommands to identify and list unused resources. The avai - `statefulsets`: Gets unused service accounts for the specified namespace or all namespaces. - `role`: Gets unused roles for the specified namespace or all namespaces. +### Supported Flags +``` +-h, --help help for role +-k, --kubeconfig string Path to kubeconfig file (optional) +-n, --namespace string Namespace to run on +--output string Output format (table or json) (default "table") +``` + To use a specific subcommand, run `kor [subcommand] [flags]`. ```sh @@ -53,6 +61,31 @@ kor [subcommand] --help | Roles | Roles not used in roleBinding | | +## Import Option +You can also use kor as a Go library to programmatically discover unused resources. By importing the github.com/yonahd/kor/pkg/kor package, you can call the relevant functions to retrieve unused resources. The library provides the option to get the results in JSON format by specifying the outputFormat parameter. + +```go +import ( +"github.com/yonahd/kor/pkg/kor" +) + +func main() { +namespace := "my-namespace" +outputFormat := "json" // Set to "json" for JSON output + + if outputFormat == "json" { + jsonResponse, err := kor.GetUnusedDeploymentsJSON(namespace) + if err != nil { + // Handle error + } + // Process the JSON response + // ... + } else { + kor.GetUnusedDeployments(namespace) + } +} +``` + ## Contributing diff --git a/cmd/kor/all.go b/cmd/kor/all.go index 2bc258b8..dacd4b90 100644 --- a/cmd/kor/all.go +++ b/cmd/kor/all.go @@ -11,15 +11,16 @@ var allCmd = &cobra.Command{ Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { if outputFormat == "json" { - kor.GetUnusedAllJSON(namespace) + kor.GetUnusedAllJSON(namespace, kubeconfig) } else { - kor.GetUnusedAll(namespace) + kor.GetUnusedAll(namespace, kubeconfig) } }, } func init() { + allCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "k", "", "Path to kubeconfig file (optional)") allCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Namespace to run on") allCmd.PersistentFlags().StringVar(&outputFormat, "output", "table", "Output format (table or json)") rootCmd.AddCommand(allCmd) diff --git a/cmd/kor/configmaps.go b/cmd/kor/configmaps.go index 4004e852..d935e535 100644 --- a/cmd/kor/configmaps.go +++ b/cmd/kor/configmaps.go @@ -12,15 +12,16 @@ var configmapCmd = &cobra.Command{ Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { if outputFormat == "json" { - kor.GetUnusedConfigmapsJSON(namespace) + kor.GetUnusedConfigmapsJSON(namespace, kubeconfig) } else { - kor.GetUnusedConfigmaps(namespace) + kor.GetUnusedConfigmaps(namespace, kubeconfig) } }, } func init() { + configmapCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "k", "", "Path to kubeconfig file (optional)") configmapCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Namespace to run on") configmapCmd.PersistentFlags().StringVar(&outputFormat, "output", "table", "Output format (table or json)") rootCmd.AddCommand(configmapCmd) diff --git a/cmd/kor/deployments.go b/cmd/kor/deployments.go index a1ab37fc..05e27eac 100644 --- a/cmd/kor/deployments.go +++ b/cmd/kor/deployments.go @@ -11,12 +11,18 @@ var deployCmd = &cobra.Command{ Short: "Gets unused deployments", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { - kor.GetUnusedDeployments(namespace) + if outputFormat == "json" { + kor.GetUnusedDeploymentsJSON(namespace, kubeconfig) + } else { + kor.GetUnusedDeployments(namespace, kubeconfig) + } }, } func init() { + deployCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "k", "", "Path to kubeconfig file (optional)") deployCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Namespace to run on") + deployCmd.PersistentFlags().StringVar(&outputFormat, "output", "table", "Output format (table or json)") rootCmd.AddCommand(deployCmd) } diff --git a/cmd/kor/roles.go b/cmd/kor/roles.go index 103e33c1..2f1200fd 100644 --- a/cmd/kor/roles.go +++ b/cmd/kor/roles.go @@ -11,14 +11,15 @@ var roleCmd = &cobra.Command{ Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { if outputFormat == "json" { - kor.GetUnusedRolesJSON(namespace) + kor.GetUnusedRolesJSON(namespace, kubeconfig) } else { - kor.GetUnusedRoles(namespace) + kor.GetUnusedRoles(namespace, kubeconfig) } }, } func init() { + roleCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "k", "", "Path to kubeconfig file (optional)") roleCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Namespace to run on") roleCmd.PersistentFlags().StringVar(&outputFormat, "output", "table", "Output format (table or json)") rootCmd.AddCommand(roleCmd) diff --git a/cmd/kor/root.go b/cmd/kor/root.go index 52cf51b5..acd23392 100644 --- a/cmd/kor/root.go +++ b/cmd/kor/root.go @@ -18,9 +18,9 @@ var rootCmd = &cobra.Command{ var namespace string var outputFormat string +var kubeconfig string func Execute() { - if err := rootCmd.Execute(); err != nil { fmt.Fprintf(os.Stderr, "Error while executing your CLI '%s'", err) os.Exit(1) diff --git a/cmd/kor/secrets.go b/cmd/kor/secrets.go index ef78f290..ef54e606 100644 --- a/cmd/kor/secrets.go +++ b/cmd/kor/secrets.go @@ -11,12 +11,18 @@ var secretCmd = &cobra.Command{ Short: "Gets unused secrets", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - kor.GetUnusedSecrets(namespace) + if outputFormat == "json" { + kor.GetUnusedSecretsJSON(namespace, kubeconfig) + } else { + kor.GetUnusedSecrets(namespace, kubeconfig) + } }, } func init() { + secretCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "k", "", "Path to kubeconfig file (optional)") secretCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Namespace to run on") + secretCmd.PersistentFlags().StringVar(&outputFormat, "output", "table", "Output format (table or json)") rootCmd.AddCommand(secretCmd) } diff --git a/cmd/kor/serviceaccounts.go b/cmd/kor/serviceaccounts.go index 46b25811..436ffbf8 100644 --- a/cmd/kor/serviceaccounts.go +++ b/cmd/kor/serviceaccounts.go @@ -11,12 +11,18 @@ var serviceAccountCmd = &cobra.Command{ Short: "Gets unused service accounts", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { - kor.GetUnusedServiceAccounts(namespace) + if outputFormat == "json" { + kor.GetUnusedServiceAccountsJSON(namespace, kubeconfig) + } else { + kor.GetUnusedServiceAccounts(namespace, kubeconfig) + } }, } func init() { + serviceAccountCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "k", "", "Path to kubeconfig file (optional)") serviceAccountCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Namespace to run on") + serviceAccountCmd.PersistentFlags().StringVar(&outputFormat, "output", "table", "Output format (table or json)") rootCmd.AddCommand(serviceAccountCmd) } diff --git a/cmd/kor/services.go b/cmd/kor/services.go index 1ec30196..a233a296 100644 --- a/cmd/kor/services.go +++ b/cmd/kor/services.go @@ -11,12 +11,18 @@ var serviceCmd = &cobra.Command{ Short: "Gets unused services", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { - kor.GetUnusedServices(namespace) + if outputFormat == "json" { + kor.GetUnusedServicesJSON(namespace, kubeconfig) + } else { + kor.GetUnusedServices(namespace, kubeconfig) + } }, } func init() { + serviceCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "k", "", "Path to kubeconfig file (optional)") serviceCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Namespace to run on") + serviceCmd.PersistentFlags().StringVar(&outputFormat, "output", "table", "Output format (table or json)") rootCmd.AddCommand(serviceCmd) } diff --git a/cmd/kor/statefulsets.go b/cmd/kor/statefulsets.go index 81f66b57..e15018b7 100644 --- a/cmd/kor/statefulsets.go +++ b/cmd/kor/statefulsets.go @@ -11,12 +11,18 @@ var stsCmd = &cobra.Command{ Short: "Gets unused statefulsets", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { - kor.GetUnusedStatefulsets(namespace) + if outputFormat == "json" { + kor.GetUnusedStatefulsetsJSON(namespace, kubeconfig) + } else { + kor.GetUnusedStatefulsets(namespace, kubeconfig) + } }, } func init() { + stsCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "k", "", "Path to kubeconfig file (optional)") stsCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "Namespace to run on") + stsCmd.PersistentFlags().StringVar(&outputFormat, "output", "table", "Output format (table or json)") rootCmd.AddCommand(stsCmd) } diff --git a/pkg/kor/all.go b/pkg/kor/all.go index 7f1d9bc8..d2ea7fba 100644 --- a/pkg/kor/all.go +++ b/pkg/kor/all.go @@ -80,12 +80,12 @@ func getUnusedRoles(kubeClient *kubernetes.Clientset, namespace string) Resource return namespaceSADiff } -func GetUnusedAll(namespace string) { +func GetUnusedAll(namespace string, kubeconfig string) { var kubeClient *kubernetes.Clientset var namespaces []string var allDiffs []ResourceDiff - kubeClient = GetKubeClient() + kubeClient = GetKubeClient(kubeconfig) namespaces = SetNamespaceList(namespace, kubeClient) for _, namespace := range namespaces { @@ -109,11 +109,11 @@ func GetUnusedAll(namespace string) { } } -func GetUnusedAllJSON(namespace string) (string, error) { +func GetUnusedAllJSON(namespace string, kubeconfig string) (string, error) { var kubeClient *kubernetes.Clientset var namespaces []string - kubeClient = GetKubeClient() + kubeClient = GetKubeClient(kubeconfig) namespaces = SetNamespaceList(namespace, kubeClient) diff --git a/pkg/kor/confimgmaps.go b/pkg/kor/confimgmaps.go index 78e90e85..9b297585 100644 --- a/pkg/kor/confimgmaps.go +++ b/pkg/kor/confimgmaps.go @@ -122,11 +122,11 @@ func processNamespaceCM(kubeClient *kubernetes.Clientset, namespace string) ([]s } -func GetUnusedConfigmaps(namespace string) { +func GetUnusedConfigmaps(namespace string, kubeconfig string) { var kubeClient *kubernetes.Clientset var namespaces []string - kubeClient = GetKubeClient() + kubeClient = GetKubeClient(kubeconfig) namespaces = SetNamespaceList(namespace, kubeClient) @@ -142,11 +142,11 @@ func GetUnusedConfigmaps(namespace string) { } } -func GetUnusedConfigmapsJSON(namespace string) (string, error) { +func GetUnusedConfigmapsJSON(namespace string, kubeconfig string) (string, error) { var kubeClient *kubernetes.Clientset var namespaces []string - kubeClient = GetKubeClient() + kubeClient = GetKubeClient(kubeconfig) namespaces = SetNamespaceList(namespace, kubeClient) response := make(map[string]map[string][]string) @@ -157,7 +157,7 @@ func GetUnusedConfigmapsJSON(namespace string) (string, error) { continue } resourceMap := make(map[string][]string) - resourceMap["configmap"] = diff + resourceMap["ConfigMap"] = diff response[namespace] = resourceMap } diff --git a/pkg/kor/deployments.go b/pkg/kor/deployments.go index aa454f60..d3f13a52 100644 --- a/pkg/kor/deployments.go +++ b/pkg/kor/deployments.go @@ -2,6 +2,7 @@ package kor import ( "context" + "encoding/json" "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -35,11 +36,11 @@ func ProcessNamespaceDeployments(clientset *kubernetes.Clientset, namespace stri } -func GetUnusedDeployments(namespace string) { +func GetUnusedDeployments(namespace string, kubeconfig string) { var kubeClient *kubernetes.Clientset var namespaces []string - kubeClient = GetKubeClient() + kubeClient = GetKubeClient(kubeconfig) namespaces = SetNamespaceList(namespace, kubeClient) @@ -54,3 +55,30 @@ func GetUnusedDeployments(namespace string) { fmt.Println() } } + +func GetUnusedDeploymentsJSON(namespace string, kubeconfig string) (string, error) { + var kubeClient *kubernetes.Clientset + var namespaces []string + + kubeClient = GetKubeClient(kubeconfig) + namespaces = SetNamespaceList(namespace, kubeClient) + response := make(map[string]map[string][]string) + + for _, namespace := range namespaces { + diff, err := ProcessNamespaceDeployments(kubeClient, namespace) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err) + continue + } + resourceMap := make(map[string][]string) + resourceMap["Deployments"] = diff + response[namespace] = resourceMap + } + + jsonResponse, err := json.MarshalIndent(response, "", " ") + if err != nil { + return "", err + } + + return string(jsonResponse), nil +} diff --git a/pkg/kor/kor.go b/pkg/kor/kor.go index b51b4f2f..87ea3eba 100644 --- a/pkg/kor/kor.go +++ b/pkg/kor/kor.go @@ -37,9 +37,10 @@ func GetKubeConfigPath() string { return filepath.Join(home, ".kube", "config") } -func GetKubeClient() *kubernetes.Clientset { - var kubeconfig string - kubeconfig = GetKubeConfigPath() +func GetKubeClient(kubeconfig string) *kubernetes.Clientset { + if kubeconfig == "" { + kubeconfig = GetKubeConfigPath() + } config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { fmt.Fprintf(os.Stderr, "Failed to load kubeconfig: %v\n", err) diff --git a/pkg/kor/roles.go b/pkg/kor/roles.go index 7994a958..e13ea31e 100644 --- a/pkg/kor/roles.go +++ b/pkg/kor/roles.go @@ -83,11 +83,11 @@ func processNamespaceRoles(kubeClient *kubernetes.Clientset, namespace string) ( } -func GetUnusedRoles(namespace string) { +func GetUnusedRoles(namespace string, kubeconfig string) { var kubeClient *kubernetes.Clientset var namespaces []string - kubeClient = GetKubeClient() + kubeClient = GetKubeClient(kubeconfig) namespaces = SetNamespaceList(namespace, kubeClient) for _, namespace := range namespaces { @@ -102,24 +102,22 @@ func GetUnusedRoles(namespace string) { } } -func GetUnusedRolesJSON(namespace string) (string, error) { +func GetUnusedRolesJSON(namespace string, kubeconfig string) (string, error) { var kubeClient *kubernetes.Clientset var namespaces []string - kubeClient = GetKubeClient() + kubeClient = GetKubeClient(kubeconfig) namespaces = SetNamespaceList(namespace, kubeClient) response := make(map[string]map[string][]string) for _, namespace := range namespaces { - var allDiffs []ResourceDiff - - namespaceRoleDiff := getUnusedRoles(kubeClient, namespace) - allDiffs = append(allDiffs, namespaceRoleDiff) - - resourceMap := make(map[string][]string) - for _, diff := range allDiffs { - resourceMap[diff.resourceType] = diff.diff + diff, err := processNamespaceRoles(kubeClient, namespace) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err) + continue } + resourceMap := make(map[string][]string) + resourceMap["Roles"] = diff response[namespace] = resourceMap } diff --git a/pkg/kor/secrets.go b/pkg/kor/secrets.go index 857d2ef6..18919e06 100644 --- a/pkg/kor/secrets.go +++ b/pkg/kor/secrets.go @@ -2,6 +2,7 @@ package kor import ( "context" + "encoding/json" "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -150,11 +151,11 @@ func processNamespaceSecret(kubeClient *kubernetes.Clientset, namespace string) } -func GetUnusedSecrets(namespace string) { +func GetUnusedSecrets(namespace string, kubeconfig string) { var kubeClient *kubernetes.Clientset var namespaces []string - kubeClient = GetKubeClient() + kubeClient = GetKubeClient(kubeconfig) namespaces = SetNamespaceList(namespace, kubeClient) @@ -169,3 +170,30 @@ func GetUnusedSecrets(namespace string) { fmt.Println() } } + +func GetUnusedSecretsJSON(namespace string, kubeconfig string) (string, error) { + var kubeClient *kubernetes.Clientset + var namespaces []string + + kubeClient = GetKubeClient(kubeconfig) + namespaces = SetNamespaceList(namespace, kubeClient) + response := make(map[string]map[string][]string) + + for _, namespace := range namespaces { + diff, err := processNamespaceSecret(kubeClient, namespace) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err) + continue + } + resourceMap := make(map[string][]string) + resourceMap["Secrets"] = diff + response[namespace] = resourceMap + } + + jsonResponse, err := json.MarshalIndent(response, "", " ") + if err != nil { + return "", err + } + + return string(jsonResponse), nil +} diff --git a/pkg/kor/serviceaccounts.go b/pkg/kor/serviceaccounts.go index d2d1d6a5..5a148b55 100644 --- a/pkg/kor/serviceaccounts.go +++ b/pkg/kor/serviceaccounts.go @@ -2,6 +2,7 @@ package kor import ( "context" + "encoding/json" "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -118,11 +119,11 @@ func processNamespaceSA(kubeClient *kubernetes.Clientset, namespace string) ([]s } -func GetUnusedServiceAccounts(namespace string) { +func GetUnusedServiceAccounts(namespace string, kubeconfig string) { var kubeClient *kubernetes.Clientset var namespaces []string - kubeClient = GetKubeClient() + kubeClient = GetKubeClient(kubeconfig) namespaces = SetNamespaceList(namespace, kubeClient) @@ -137,3 +138,30 @@ func GetUnusedServiceAccounts(namespace string) { fmt.Println() } } + +func GetUnusedServiceAccountsJSON(namespace string, kubeconfig string) (string, error) { + var kubeClient *kubernetes.Clientset + var namespaces []string + + kubeClient = GetKubeClient(kubeconfig) + namespaces = SetNamespaceList(namespace, kubeClient) + response := make(map[string]map[string][]string) + + for _, namespace := range namespaces { + diff, err := processNamespaceSA(kubeClient, namespace) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err) + continue + } + resourceMap := make(map[string][]string) + resourceMap["ServiceAccounts"] = diff + response[namespace] = resourceMap + } + + jsonResponse, err := json.MarshalIndent(response, "", " ") + if err != nil { + return "", err + } + + return string(jsonResponse), nil +} diff --git a/pkg/kor/services.go b/pkg/kor/services.go index adb51ec1..3db1e837 100644 --- a/pkg/kor/services.go +++ b/pkg/kor/services.go @@ -2,6 +2,7 @@ package kor import ( "context" + "encoding/json" "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -35,11 +36,11 @@ func ProcessNamespaceServices(clientset *kubernetes.Clientset, namespace string) } -func GetUnusedServices(namespace string) { +func GetUnusedServices(namespace string, kubeconfig string) { var kubeClient *kubernetes.Clientset var namespaces []string - kubeClient = GetKubeClient() + kubeClient = GetKubeClient(kubeconfig) namespaces = SetNamespaceList(namespace, kubeClient) @@ -54,3 +55,30 @@ func GetUnusedServices(namespace string) { fmt.Println() } } + +func GetUnusedServicesJSON(namespace string, kubeconfig string) (string, error) { + var kubeClient *kubernetes.Clientset + var namespaces []string + + kubeClient = GetKubeClient(kubeconfig) + namespaces = SetNamespaceList(namespace, kubeClient) + response := make(map[string]map[string][]string) + + for _, namespace := range namespaces { + diff, err := ProcessNamespaceServices(kubeClient, namespace) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err) + continue + } + resourceMap := make(map[string][]string) + resourceMap["Services"] = diff + response[namespace] = resourceMap + } + + jsonResponse, err := json.MarshalIndent(response, "", " ") + if err != nil { + return "", err + } + + return string(jsonResponse), nil +} diff --git a/pkg/kor/statefulsets.go b/pkg/kor/statefulsets.go index b9c38008..ce763357 100644 --- a/pkg/kor/statefulsets.go +++ b/pkg/kor/statefulsets.go @@ -2,6 +2,7 @@ package kor import ( "context" + "encoding/json" "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -35,11 +36,11 @@ func ProcessNamespaceStatefulsets(clientset *kubernetes.Clientset, namespace str } -func GetUnusedStatefulsets(namespace string) { +func GetUnusedStatefulsets(namespace string, kubeconfig string) { var kubeClient *kubernetes.Clientset var namespaces []string - kubeClient = GetKubeClient() + kubeClient = GetKubeClient(kubeconfig) namespaces = SetNamespaceList(namespace, kubeClient) @@ -54,3 +55,30 @@ func GetUnusedStatefulsets(namespace string) { fmt.Println() } } + +func GetUnusedStatefulsetsJSON(namespace string, kubeconfig string) (string, error) { + var kubeClient *kubernetes.Clientset + var namespaces []string + + kubeClient = GetKubeClient(kubeconfig) + namespaces = SetNamespaceList(namespace, kubeClient) + response := make(map[string]map[string][]string) + + for _, namespace := range namespaces { + diff, err := ProcessNamespaceStatefulsets(kubeClient, namespace) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err) + continue + } + resourceMap := make(map[string][]string) + resourceMap["Statefulsets"] = diff + response[namespace] = resourceMap + } + + jsonResponse, err := json.MarshalIndent(response, "", " ") + if err != nil { + return "", err + } + + return string(jsonResponse), nil +}