Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support json response and kubeconfig flag #13

Merged
merged 7 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -49,10 +57,35 @@ kor [subcommand] --help
| Services | Services with no endpoints | |
| Deployments | Deployments with 0 Replicas | |
| ServiceAccounts | ServiceAccounts unused by pods<br/>ServiceAccounts unused by roleBinding or clusterRoleBinding | |
| Statefulsets | Statefulsets with no endpoints | |
| Statefulsets | Statefulsets with 0 Replicas | |
| 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

Expand Down
8 changes: 7 additions & 1 deletion cmd/kor/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@ 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, kubeconfig)
} else {
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)
}
8 changes: 7 additions & 1 deletion cmd/kor/configmaps.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,18 @@ 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, kubeconfig)
} else {
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)
}
8 changes: 7 additions & 1 deletion cmd/kor/deployments.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
9 changes: 7 additions & 2 deletions cmd/kor/roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ 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, kubeconfig)
} else {
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)
}
3 changes: 2 additions & 1 deletion cmd/kor/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ 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)
Expand Down
8 changes: 7 additions & 1 deletion cmd/kor/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
8 changes: 7 additions & 1 deletion cmd/kor/serviceaccounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
8 changes: 7 additions & 1 deletion cmd/kor/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
8 changes: 7 additions & 1 deletion cmd/kor/statefulsets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
62 changes: 60 additions & 2 deletions pkg/kor/all.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -74,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 {
Expand All @@ -102,3 +108,55 @@ func GetUnusedAll(namespace string) {
fmt.Println()
}
}

func GetUnusedAllJSON(namespace string, kubeconfig string) (string, error) {
var kubeClient *kubernetes.Clientset
var namespaces []string

kubeClient = GetKubeClient(kubeconfig)

namespaces = SetNamespaceList(namespace, kubeClient)

// Create the JSON response object
response := make(map[string]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 for each resource type in the JSON response
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
}

return string(jsonResponse), nil
}
32 changes: 30 additions & 2 deletions pkg/kor/confimgmaps.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package kor

import (
"context"
"encoding/json"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
Expand Down Expand Up @@ -121,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)

Expand All @@ -140,3 +141,30 @@ func GetUnusedConfigmaps(namespace string) {
fmt.Println()
}
}

func GetUnusedConfigmapsJSON(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 := processNamespaceCM(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["ConfigMap"] = diff
response[namespace] = resourceMap
}

jsonResponse, err := json.MarshalIndent(response, "", " ")
if err != nil {
return "", err
}

return string(jsonResponse), nil
}
Loading
Loading