Skip to content

Commit

Permalink
separate data extraction from printing; add options and formats
Browse files Browse the repository at this point in the history
  • Loading branch information
mattfenwick committed Jul 28, 2020
1 parent 4ca96bd commit 4a8537f
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 76 deletions.
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.14

require (
github.com/fatih/color v1.9.0
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v1.0.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
Expand All @@ -325,6 +326,7 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5 h1:58+kh9C6jJVXYjt8IE48G2eWl6BjwU5Gj0gqY84fy78=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
Expand Down
232 changes: 189 additions & 43 deletions pkg/cli/root.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
package cli

import (
"encoding/json"
"fmt"
"github.com/mattfenwick/krew-node-pod/pkg/plugin"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
//"os"
"os"
"sort"
"strings"

//"strings"
//"time"

"github.com/olekukonko/tablewriter"
log "github.com/sirupsen/logrus"
//"github.com/mattfenwick/krew-node-pod/pkg/logger"
//"github.com/mattfenwick/krew-node-pod/pkg/plugin"
//"github.com/pkg/errors"
"github.com/spf13/cobra"
//"github.com/spf13/viper"
//"github.com/tj/go-spin"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

Expand All @@ -26,20 +22,28 @@ func doOrDie(err error) {
}
}

func InitAndExecute() {
rootCmd := setupRootCmd()
if err := rootCmd.Execute(); err != nil {
log.Fatalf("%+v", err)
}
}

type Config struct {
LogLevel string
KubeFlags *genericclioptions.ConfigFlags
LogLevel string
KubeFlags *genericclioptions.ConfigFlags
ShowContainers bool
Format string
}

func setupRootCmd() *cobra.Command {
args := &Config{}

cmd := &cobra.Command{
Use: "node-pod",
Short: "",
Long: `.`,
SilenceErrors: true,
SilenceUsage: true,
Use: "node-pod",
Short: "",
Long: `.`,
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, as []string) error {
// TODO detect whether this is running under kubectl or not, and modify help message accordingly
// see https://krew.sigs.k8s.io/docs/developer-guide/develop/best-practices/#auth-plugins
Expand All @@ -51,62 +55,204 @@ func setupRootCmd() *cobra.Command {

cmd.Flags().StringVar(&args.LogLevel, "v", "info", "log level")

cmd.Flags().StringVar(&args.Format, "format", "list", "output format: one of json, list, table")

cmd.Flags().BoolVarP(&args.ShowContainers, "containers", "c", false, "if true, print containers")

args.KubeFlags = genericclioptions.NewConfigFlags(false)
args.KubeFlags.AddFlags(cmd.Flags())

return cmd
}

func InitAndExecute() {
rootCmd := setupRootCmd()
if err := rootCmd.Execute(); err != nil {
log.Fatalf("%+v", err)
}
}

func runRootCmd(args *Config) {
level, err := log.ParseLevel(args.LogLevel)
doOrDie(err)
log.SetLevel(level)

// TODO use args
ListPods()
}

func ListPods() {
client, err := plugin.NewDefaultClient()
client, err := plugin.NewClientWithDefaultKubeConfigFallback(*args.KubeFlags.KubeConfig)
doOrDie(err)

pods, err := client.ListPods(v1.NamespaceAll)
output, err := FetchKubeData(client, *args.KubeFlags.Namespace)
doOrDie(err)

nodeToPods := map[string][]v1.Pod{}
for _, pod := range pods.Items {
node := pod.Spec.NodeName
nodeToPods[node] = append(nodeToPods[node], pod)
if !args.ShowContainers {
output.RemoveContainers()
}

nodes := []string{}
for node := range nodeToPods {
nodes = append(nodes, node)
switch args.Format {
case "list":
fmt.Println(output.List())
case "json":
fmt.Println(output.Json())
case "table":
output.Table().Render()
default:
doOrDie(errors.Errorf("invalid format '%s'", args.Format))
}
}

type Output struct {
Nodes []*Node
}

func NewOutput(nodeMap map[string]*Node) *Output {
var nodes []*Node
for _, node := range nodeMap {
nodes = append(nodes, node)
}
sort.Slice(nodes, func(i, j int) bool {
return nodes[i] < nodes[j]
return nodes[i].Name < nodes[j].Name
})

for _, node := range nodes {
pods := nodeToPods[node]
pods := node.Pods
sort.Slice(pods, func(i, j int) bool {
if pods[i].Namespace != pods[j].Namespace {
return pods[i].Namespace < pods[j].Namespace
}
return pods[i].Name < pods[j].Name
})
fmt.Printf("%s:\n", node)
node.Pods = pods
for _, pod := range pods {
fmt.Printf(" - %s/%s\n", pod.Namespace, pod.Name)
containers := pod.Containers
sort.Slice(containers, func(i, j int) bool {
return containers[i].Name < containers[j].Name
})
pod.Containers = containers
}
}
return &Output{Nodes: nodes}
}

func (o *Output) RemoveContainers() {
for _, node := range o.Nodes {
for _, pod := range node.Pods {
pod.Containers = nil
}
}
}

func (o *Output) Json() string {
bytes, err := json.MarshalIndent(o, "", " ")
doOrDie(err)
return string(bytes)
}

func (o *Output) Table() *tablewriter.Table {
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Node", "Namespace", "Pod Name", "Container", "Status"})

for _, node := range o.Nodes {
table.Append([]string{node.Name, "", "", "", node.Status})

for _, pod := range node.Pods {
table.Append([]string{"", pod.Namespace, pod.Name, "", pod.Status})

for _, container := range pod.Containers {
table.Append([]string{"", "", "", container.Name, container.Status})
}
}
}

return table
}

func (o *Output) List() string {
var lines []string
for _, node := range o.Nodes {
lines = append(lines, fmt.Sprintf("%s: %s", node.Name, node.Status))
for _, pod := range node.Pods {
lines = append(lines, fmt.Sprintf(" - %s/%s: %s", pod.Namespace, pod.Name, pod.Status))
for _, container := range pod.Containers {
lines = append(lines, fmt.Sprintf(" - %s: %s", container.Name, container.Status))
}
}
lines = append(lines, "")
}
return strings.Join(lines, "\n")
}

type Node struct {
Name string
Pods []*Pod
Status string
}

type Pod struct {
Name string
Namespace string
Containers []*Container
Status string
}

type Container struct {
Name string
Status string
}

func FetchKubeData(client *plugin.Client, namespace string) (*Output, error) {
kubePods, err := client.ListPods(namespace)
if err != nil {
return nil, err
}

kubeNodes, err := client.ListNodes()
if err != nil {
return nil, err
}

// get the nodes
nodes := map[string]*Node{}
for _, kubeNode := range kubeNodes.Items {
var status = "unknown"
if len(kubeNode.Status.Conditions) > 0 {
status = string(kubeNode.Status.Conditions[len(kubeNode.Status.Conditions)-1].Type)
}
nodes[kubeNode.Name] = &Node{
Name: kubeNode.Name,
Pods: nil,
Status: status,
}
}

// add the pods into the nodes
for _, kubePod := range kubePods.Items {
nodeName := kubePod.Spec.NodeName
node, ok := nodes[nodeName]
if !ok {
return nil, errors.Errorf("pod %s/%s assigned to node %s -- but node not found in kube", kubePod.Namespace, kubePod.Name, nodeName)
}
fmt.Println()
// TODO methodize this
node.Pods = append(node.Pods, extractPod(&kubePod))
}

return NewOutput(nodes), err
}

func extractPod(kubePod *v1.Pod) *Pod {
var containers []*Container
for _, cont := range kubePod.Status.ContainerStatuses {
containers = append(containers, extractContainer(&cont))
}
return &Pod{
Name: kubePod.Name,
Namespace: kubePod.Namespace,
Containers: containers,
Status: string(kubePod.Status.Phase),
}
}

func extractContainer(kubeContainer *v1.ContainerStatus) *Container {
state := "unknown"
if kubeContainer.State.Running != nil {
state = "running"
} else if kubeContainer.State.Terminated != nil {
state = "terminated"
} else if kubeContainer.State.Waiting != nil {
state = "waiting"
}
return &Container{
Name: kubeContainer.Name,
Status: state,
}
}
15 changes: 14 additions & 1 deletion pkg/plugin/kubeclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import (
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth" // required for auth, see: https://github.com/kubernetes/client-go/tree/v0.17.3/plugin/pkg/client/auth
// required for auth, see: https://github.com/kubernetes/client-go/tree/v0.17.3/plugin/pkg/client/auth
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/tools/clientcmd"
)

Expand All @@ -33,6 +34,13 @@ func NewDefaultClient() (*Client, error) {
return NewClient(kubeConfigPath)
}

func NewClientWithDefaultKubeConfigFallback(kubeConfigPath string) (*Client, error) {
if kubeConfigPath == "" {
return NewDefaultClient()
}
return NewClient(kubeConfigPath)
}

func NewClient(kubeConfigPath string) (*Client, error) {
log.Debugf("instantiating k8s client from config path: '%s'", kubeConfigPath)
kubeConfig, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath)
Expand All @@ -53,3 +61,8 @@ func (kc *Client) ListPods(namespace string) (*v1.PodList, error) {
pods, err := kc.client.CoreV1().Pods(namespace).List(metav1.ListOptions{})
return pods, errors.Wrapf(err, "unable to list pods in ns '%s'", namespace)
}

func (kc *Client) ListNodes() (*v1.NodeList, error) {
nodes, err := kc.client.CoreV1().Nodes().List(metav1.ListOptions{})
return nodes, errors.Wrapf(err, "unable to list nodes")
}
32 changes: 0 additions & 32 deletions pkg/plugin/plugin.go

This file was deleted.

0 comments on commit 4a8537f

Please sign in to comment.