Skip to content

Commit

Permalink
add support for json output (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
nilic authored Dec 18, 2022
1 parent 1c5a5d5 commit 86dee54
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 50 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ Flags:
-h, --help help for kubectl-unlimited
--kubeconfig string path to the kubeconfig file
-l, --labels string labels to filter pods with
-n, --namespace string only analyze pods in this namespace (by default all pods from all namespaces are shown)
-n, --namespace string only analyze containers in this namespace (by default all containers from all namespaces are shown)
-o, --output string output format, one of: [table json] (default "table")
```

## Examples
Expand All @@ -37,6 +38,9 @@ Flags:
# get containers with either CPU or memory limits unset
$ kubectl unlimited
# same, but in JSON
$ kubectl unlimited -o json
# get containers with only CPU limits unset
$ kubectl unlimited cpu
Expand Down
2 changes: 1 addition & 1 deletion cmd/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var cpuCmd = &cobra.Command{
Short: "Display information about running containers with no CPU limits set",
Long: `Display information about running containers with no CPU limits set`,
Run: func(cmd *cobra.Command, args []string) {
unlimited.ShowUnlimited(kubeConfig, kubeContext, namespace, labels, true, false)
unlimited.ShowUnlimited(kubeConfig, kubeContext, namespace, labels, outputFormat, true, false)
},
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var memoryCmd = &cobra.Command{
Short: "Display information about running containers with no memory limits set",
Long: `Display information about running containers with no memory limits set`,
Run: func(cmd *cobra.Command, args []string) {
unlimited.ShowUnlimited(kubeConfig, kubeContext, namespace, labels, false, true)
unlimited.ShowUnlimited(kubeConfig, kubeContext, namespace, labels, outputFormat, false, true)
},
}

Expand Down
26 changes: 18 additions & 8 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
package cmd

import (
"os"
"fmt"
"log"

"github.com/nilic/kubectl-unlimited/unlimited"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
)

var (
kubeConfig string
kubeContext string
namespace string
labels string
kubeConfig string
kubeContext string
namespace string
labels string
outputFormat string

rootCmd = &cobra.Command{
Use: "kubectl-unlimited",
Short: "kubectl plugin for displaying information about running containers with no limits set.",
Long: "kubectl plugin for displaying information about running containers with no limits set.",

PersistentPreRun: func(cmd *cobra.Command, args []string) {
if !slices.Contains(unlimited.SupportedOutputFormats, outputFormat) {
log.Fatalf("error: invalid output format, please choose one of: %v\n", unlimited.SupportedOutputFormats)
}
},
Run: func(cmd *cobra.Command, args []string) {
unlimited.ShowUnlimited(kubeConfig, kubeContext, namespace, labels, true, true)
unlimited.ShowUnlimited(kubeConfig, kubeContext, namespace, labels, outputFormat, true, true)
},
}
)

func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
log.Fatalf("error: %v\n", err)
}
}

Expand All @@ -40,4 +47,7 @@ func init() {
"namespace", "n", "", "only analyze containers in this namespace (by default all containers from all namespaces are shown)")
rootCmd.PersistentFlags().StringVarP(&labels,
"labels", "l", "", "labels to filter pods with")
rootCmd.PersistentFlags().StringVarP(&outputFormat,
"output", "o", "table",
fmt.Sprintf("output format, one of: %v", unlimited.SupportedOutputFormats))
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.19

require (
github.com/spf13/cobra v1.6.1
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15
k8s.io/api v0.26.0
k8s.io/apimachinery v0.26.0
k8s.io/client-go v0.26.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 h1:5oN1Pz/eDhCpbMbLstvIPa0b/BEQo6g6nwV3pLjfM6w=
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
Expand Down
35 changes: 18 additions & 17 deletions unlimited/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,17 @@ import (
"k8s.io/client-go/kubernetes"
)

type computeResources struct {
CPURequest resource.Quantity
CPULimit resource.Quantity
memoryRequest resource.Quantity
memoryLimit resource.Quantity
type computeResource struct {
CPU resource.Quantity `json:"cpu"`
Memory resource.Quantity `json:"memory"`
}

type container struct {
name string
podName string
namespace string
resources computeResources
Name string `json:"name"`
PodName string `json:"pod"`
Namespace string `json:"namespace"`
Limits computeResource `json:"limits"`
Requests computeResource `json:"requests"`
}

func getPods(clientset kubernetes.Interface, namespace string, labels string) (*corev1.PodList, error) {
Expand Down Expand Up @@ -52,14 +51,16 @@ func buildContainerList(pods *corev1.PodList, checkCPU bool, checkMemory bool) [
for _, c := range p.Spec.Containers {
if (checkCPU && c.Resources.Limits.Cpu().IsZero()) || (checkMemory && c.Resources.Limits.Memory().IsZero()) {
containerList = append(containerList, container{
name: c.Name,
podName: p.Name,
namespace: p.Namespace,
resources: computeResources{
CPURequest: c.Resources.Requests["cpu"],
CPULimit: c.Resources.Limits["cpu"],
memoryRequest: c.Resources.Requests["memory"],
memoryLimit: c.Resources.Limits["memory"],
Name: c.Name,
PodName: p.Name,
Namespace: p.Namespace,
Limits: computeResource{
CPU: c.Resources.Limits["cpu"],
Memory: c.Resources.Limits["memory"],
},
Requests: computeResource{
CPU: c.Resources.Requests["cpu"],
Memory: c.Resources.Requests["memory"],
},
})
}
Expand Down
63 changes: 43 additions & 20 deletions unlimited/printer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package unlimited

import (
"encoding/json"
"fmt"
"os"
"sort"
Expand All @@ -12,39 +13,61 @@ const (
mebibyte = 1024 * 1024
)

func printContainerList(containerList []container) {
sortContainerList(containerList)
var SupportedOutputFormats = []string{"table", "json"}

// (output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint)
w := tabwriter.NewWriter(os.Stdout, 6, 4, 3, ' ', 0)
fmt.Fprintln(w, header)
for _, c := range containerList {
fmt.Fprintf(w, "%s\t%s\t%s\t%dm\t%dm\t%dMi\t%dMi\n",
c.namespace,
c.podName,
c.name,
c.resources.CPURequest.MilliValue(),
c.resources.CPULimit.MilliValue(),
formatToMebibyte(c.resources.memoryRequest.Value()),
formatToMebibyte(c.resources.memoryLimit.Value()))
func printContainerList(containerList []container, outputFormat string) error {
sortContainerList(containerList)
switch outputFormat {
case "table":
return printTable(containerList)
case "json":
return printJSON(containerList)
default:
return fmt.Errorf("invalid output format, please choose one of: %v", SupportedOutputFormats)
}
w.Flush()
}

func sortContainerList(cl []container) {
sort.Slice(cl, func(i, j int) bool {
if cl[i].namespace != cl[j].namespace {
return cl[i].namespace < cl[j].namespace
if cl[i].Namespace != cl[j].Namespace {
return cl[i].Namespace < cl[j].Namespace
}

if cl[i].podName != cl[j].podName {
return cl[i].podName < cl[j].podName
if cl[i].PodName != cl[j].PodName {
return cl[i].PodName < cl[j].PodName
}

return cl[i].name < cl[j].name
return cl[i].Name < cl[j].Name
})
}

func printTable(containerList []container) error {
// (output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint)
w := tabwriter.NewWriter(os.Stdout, 6, 4, 3, ' ', 0)
fmt.Fprintln(w, header)
for _, c := range containerList {
fmt.Fprintf(w, "%s\t%s\t%s\t%dm\t%dm\t%dMi\t%dMi\n",
c.Namespace,
c.PodName,
c.Name,
c.Requests.CPU.MilliValue(),
c.Limits.CPU.MilliValue(),
formatToMebibyte(c.Requests.Memory.Value()),
formatToMebibyte(c.Limits.Memory.Value()))
}
w.Flush()
return nil
}

func printJSON(containerList []container) error {
jsonRaw, err := json.MarshalIndent(containerList, "", " ")
if err != nil {
return fmt.Errorf("error marshaling JSON: %s", err.Error())
}
fmt.Printf("%s", jsonRaw)
return nil
}

func formatToMebibyte(v int64) int64 {
valueMebibyte := v / mebibyte
if v%mebibyte != 0 {
Expand Down
7 changes: 5 additions & 2 deletions unlimited/unlimited.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"log"
)

func ShowUnlimited(kubeConfig string, kubeContext string, namespace string, labels string, checkCPU bool, checkMemory bool) {
func ShowUnlimited(kubeConfig string, kubeContext string, namespace string, labels string, outputFormat string, checkCPU bool, checkMemory bool) {
clientset, err := getKubeClientset(kubeConfig, kubeContext)
if err != nil {
log.Fatalf("error: %v\n", err)
Expand All @@ -17,5 +17,8 @@ func ShowUnlimited(kubeConfig string, kubeContext string, namespace string, labe

containerList := buildContainerList(pods, checkCPU, checkMemory)

printContainerList(containerList)
err = printContainerList(containerList, outputFormat)
if err != nil {
log.Fatalf("error: %v\n", err)
}
}

0 comments on commit 86dee54

Please sign in to comment.