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

add json output #1

Merged
merged 5 commits into from
Dec 18, 2022
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
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)
}
}